[m-rev.] for review: new implementation of string__format in mercury.
p.incani
paulai at ugrad.unimelb.edu.au
Thu Jan 31 19:32:02 AEDT 2002
Estimated hours taken: 200
Removed the old implementation of string__format because it relies on C's
printf function - this causes problems when Mercury is compiled to Jave or
.NET. Replaced it with a new version of string__format that works
independently of C's printf, and which is written solely in Mercury.
library/string.m:
Removed old implementation of string__format.
Slightly modified the format string parser.
Replaced old string__format with a new Mercury implementation.
Modified type 'specifier'.
Added quite a few maths utilities that are needed to format
floats.
Index: string.m
===================================================================
RCS file: /home/mercury1/repository/mercury/library/string.m,v
retrieving revision 1.165
diff -u -r1.165 string.m
--- string.m 20 Jan 2002 07:32:26 -0000 1.165
+++ string.m 30 Jan 2002 07:47:53 -0000
@@ -458,7 +458,7 @@
%-----------------------------------------------------------------------------%
:- implementation.
-:- import_module bool, std_util, int, float, require.
+:- import_module bool, std_util, int, float, require, integer.
:- pred string__to_int_list(string, list(int)).
:- mode string__to_int_list(out, in) is det.
@@ -1135,8 +1135,8 @@
:- type specifier
---> conv(
flags :: list(char),
- width :: maybe(list(char)),
- precision :: maybe(list(char)),
+ width :: maybe(int),
+ precision :: maybe(int),
spec :: spec
)
; string(list(char)).
@@ -1231,10 +1231,11 @@
flag(' ').
flag('+').
+
%
% Do we have a minimum field width?
%
-:- pred width(list(char)::out,
+:- pred width(int::out,
list(string__poly_type)::in, list(string__poly_type)::out,
list(char)::in, list(char)::out) is semidet.
@@ -1242,7 +1243,7 @@
( ['*'] ->
{ PolyTypes0 = [i(Width0) | PolyTypes1] ->
% XXX maybe better done in C.
- Width = to_char_list(int_to_string(Width0)),
+ Width = Width0,
PolyTypes = PolyTypes1
;
error("string__format: `*' width modifer not associated with an integer.")
@@ -1253,14 +1254,17 @@
zero_or_more_occurences(digit),
=(Final),
- { list__remove_suffix(Init, Final, Width) },
+ { list__remove_suffix(Init, Final, WidthCharList) },
+ { string__from_char_list(WidthCharList, WidthString)},
+ { string__to_int(WidthString, Width)},
{ PolyTypes = PolyTypes0 }
).
+
%
% Do we have a precision?
%
-:- pred prec(list(char)::out,
+:- pred prec(int::out,
list(string__poly_type)::in, list(string__poly_type)::out,
list(char)::in, list(char)::out) is semidet.
@@ -1269,7 +1273,7 @@
( ['*'] ->
{ PolyTypes0 = [i(Prec0) | PolyTypes1] ->
% XXX Best done in C
- Prec = to_char_list(int_to_string(Prec0)),
+ Prec = Prec0,
PolyTypes = PolyTypes1
;
error("string__format: `*' precision modifer not associated with an integer.")
@@ -1280,15 +1284,18 @@
zero_or_more_occurences(digit),
=(Final)
->
- { list__remove_suffix(Init, Final, Prec) },
+ { list__remove_suffix(Init, Final, PrecCharList) },
+ { string__from_char_list(PrecCharList, PrecString)},
+ { string__to_int(PrecString, Prec)},
{ PolyTypes = PolyTypes0 }
;
% When no number follows the '.' the precision
% defaults to 0.
- { Prec = ['0'] },
+ { Prec = 0 },
{ PolyTypes = PolyTypes0 }
).
+
% NB the capital letter specifiers are proceeded with a 'c'.
:- type spec
% valid integer specifiers
@@ -1386,247 +1393,924 @@
(
% valid int conversion specifiers
Spec = d(Int),
- String = format_int(
- make_format(Flags, Width,
- Prec, int_length_modifer, "d"), Int)
+ String = format_int(Flags, Width, Prec, Int)
;
Spec = i(Int),
- String = format_int(
- make_format(Flags, Width,
- Prec, int_length_modifer, "i"), Int)
+ String = format_int(Flags, Width, Prec, Int)
;
Spec = o(Int),
- String = format_int(
- make_format(Flags, Width,
- Prec, int_length_modifer, "o"), Int)
+ String = format_unsigned_int(Flags, Width, Prec, 8, Int, "")
;
Spec = u(Int),
- String = format_int(
- make_format(Flags, Width,
- Prec, int_length_modifer, "u"), Int)
+ String = format_unsigned_int(Flags, Width, Prec, 10, Int, "")
;
Spec = x(Int),
- String = format_int(
- make_format(Flags, Width,
- Prec, int_length_modifer, "x"), Int)
+ String = format_unsigned_int(Flags, Width, Prec, 16, Int, "0x")
;
Spec = cX(Int),
- String = format_int(
- make_format(Flags, Width,
- Prec, int_length_modifer, "X"), Int)
+ String = format_unsigned_int(Flags, Width, Prec, 16, Int, "0X")
;
Spec = p(Int),
- String = format_int(
- make_format(Flags, Width,
- Prec, int_length_modifer, "p"), Int)
+ String = format_unsigned_int(['#'|Flags], Width, Prec, 16, Int, "0x")
;
- % valid float conversion specifiers
Spec = e(Float),
- String = format_float(
- make_format(Flags, Width, Prec, "", "e"), Float)
+ String = format_scientific_number(Flags, Width, Prec, Float, "e")
;
Spec = cE(Float),
- String = format_float(
- make_format(Flags, Width, Prec, "", "E"), Float)
+ String = format_scientific_number(Flags, Width, Prec, Float, "E")
;
Spec = f(Float),
- String = format_float(
- make_format(Flags, Width, Prec, "", "f"), Float)
+ String = format_float(Flags, Width, Prec, Float)
;
Spec = cF(Float),
- String = format_float(
- make_format(Flags, Width, Prec, "", "F"), Float)
+ String = format_float(Flags, Width, Prec, Float)
;
Spec = g(Float),
- String = format_float(
- make_format(Flags, Width, Prec, "", "g"), Float)
+ String = format_scientific_number_g(Flags, Width, Prec, Float, "e")
;
Spec = cG(Float),
- String = format_float(
- make_format(Flags, Width, Prec, "", "G"), Float)
+ String = format_scientific_number_g(Flags, Width, Prec, Float, "E")
;
- % valid char conversion Specifiers
Spec = c(Char),
- String = format_char(
- make_format(Flags, Width, Prec, "", "c"), Char)
+ String = format_char(Flags, Width, Char)
;
- % valid string conversion Spec = ifiers
Spec = s(Str),
- String = format_string(
- make_format(Flags, Width, Prec, "", "s"), Str)
+ String = format_string(Flags, Width, Prec, Str)
;
- % conversion specifier representing the "%" sign
Spec = percent,
String = "%"
).
specifier_to_string(string(Chars)) = from_char_list(Chars).
+ %
+ % Format a signed int (d,i).
+ %
+:- func format_int(list(char), maybe(int), maybe(int), int) = string.
+format_int(Flags, Width, Prec, Int) = String :-
+ %
+ % Find integers absolute value, and take care of special
+ % case of precision zero with an integer of 0.
+ %
+ ( Int = 0, Prec = yes(0)
+ ->
+ AbsIntStr = ""
+ ;
+ Integer = integer(Int),
+ AbsInteger = integer__abs(Integer),
+ AbsIntStr = integer__to_string(AbsInteger)
+ ),
+ AbsIntStrLength = string__length(AbsIntStr),
+ %
+ % Do we need to increase precision?
+ %
+ (Prec = yes(Precision),
+ Precision > AbsIntStrLength
+ ->
+ PrecStr = string__pad_left(AbsIntStr, '0', Precision)
+ ;
+ PrecStr = AbsIntStr
+ ),
+ %
+ % Do we need to pad to the field width.
+ %
+ ( Width = yes(FieldWidth),
+ FieldWidth > string__length(PrecStr),
+ member('0', Flags),
+ \+member('-', Flags),
+ Prec = no
+ ->
+ FieldStr = string__pad_left(PrecStr, '0', FieldWidth - 1),
+ ZeroPadded = yes
+ ;
+ FieldStr = PrecStr,
+ ZeroPadded = no
+ ),
+ %
+ % Prefix with appropriate sign or zero padding.
+ % The previous step has deliberately left room for this.
+ %
+ ( Int < 0 ->
+ SignedStr = string__append("-", FieldStr)
+ ;
+ member('+', Flags) ->
+ SignedStr = string__append("+", FieldStr)
+ ;
+ member(' ', Flags) ->
+ SignedStr = string__append(" ", FieldStr)
+ ;
+ ZeroPadded = yes ->
+ SignedStr = string__append("0", FieldStr)
+ ;
+ SignedStr = FieldStr
+ ),
+ %
+ % Do we need to justify?
+ %
+ String = justify_string(Flags, Width, SignedStr).
+
+
+ %
+ % Format an unsigned int, unsigned octal, unsigned hexadecimal (u,o,x,X).
+ %
+:- func format_unsigned_int(list(char), maybe(int), maybe(int), int, int, string) = string.
+format_unsigned_int(Flags, Width, Prec, Base, Int, Prefix) = String :-
+ %
+ % Find integers absolute value, and take care of special
+ % case of precision zero with an integer of 0.
+ %
+ ( Int = 0, Prec = yes(0)
+ ->
+ AbsIntStr = ""
+ ;
+ integer__pow(integer(2), integer(int__bits_per_int), Div),
+ UnsignedInteger = integer(Int) mod Div,
+ ( Base = 10
+ ->
+ AbsIntStr0 = integer__to_string(UnsignedInteger)
+ ; Base = 8
+ ->
+ AbsIntStr0 = to_octal(UnsignedInteger)
+ ; Prefix = "0x"
+ ->
+ AbsIntStr0 = to_hex(UnsignedInteger)
+ ;
+ AbsIntStr0 = to_capital_hex(UnsignedInteger)
+ ),
+ %
+ % Just in case Int = 0 (base converters return "").
+ %
+ ( AbsIntStr0 = ""
+ ->
+ AbsIntStr = "0"
+ ;
+ AbsIntStr = AbsIntStr0
+ )
+ ),
+ AbsIntStrLength = string__length(AbsIntStr),
+ %
+ % Do we need to increase precision?
+ %
+ (Prec = yes(Precision),
+ Precision > AbsIntStrLength
+ ->
+ PrecStr = string__pad_left(AbsIntStr, '0', Precision)
+ ;
+ PrecStr = AbsIntStr
+ ),
+ %
+ % Do we need to increase the precision of an octal?
+ %
+ ( Base = 8,
+ member('#', Flags),
+ \+string__prefix(PrecStr, "0")
+ ->
+ PrecModStr = append("0", PrecStr)
+ ;
+ PrecModStr = PrecStr
+ ),
+ %
+ % Do we need to pad to the field width.
+ %
+ ( Width = yes(FieldWidth),
+ FieldWidth > string__length(PrecModStr),
+ member('0', Flags),
+ \+member('-', Flags),
+ Prec = no
+ ->
+ %
+ % Do we need to make room for "0x" or "0X" ?
+ %
+ (Base = 16, member('#', Flags)
+ ->
+ FieldStr = string__pad_left(PrecModStr, '0', FieldWidth - 2)
+ ;
+ FieldStr = string__pad_left(PrecModStr, '0', FieldWidth)
+ )
+ ;
+ FieldStr = PrecModStr
+ ),
+ %
+ % Do we have to prefix "0x" or "0X"?
+ %
+ ( Base = 16,
+ member('#', Flags),
+ FieldStr \= "0",
+ FieldStr \= ""
+ ->
+ FieldModStr = string__append(Prefix, FieldStr)
+ ;
+ FieldModStr = FieldStr
+ ),
+ %
+ % Do we need to justify?
+ %
+ String = justify_string(Flags, Width, FieldModStr).
+
- % Construct a format string.
-:- func make_format(list(char), maybe(list(char)),
- maybe(list(char)), string, string) = string.
+ %
+ % Format a character (c).
+ %
+:- func format_char(list(char), maybe(int), char) = string.
+format_char(Flags, Width, Char) = String :-
+ CharStr = string__char_to_string(Char),
+ String = justify_string(Flags, Width, CharStr).
-make_format(Flags, MaybeWidth, MaybePrec, LengthMod, Spec) =
- ( using_sprintf ->
- make_format_sprintf(Flags, MaybeWidth, MaybePrec, LengthMod,
- Spec)
+
+ %
+ % Format a string (s).
+ %
+:- func format_string(list(char), maybe(int), maybe(int), string) = string.
+format_string(Flags, Width, Prec, OldStr) = NewStr :-
+ ( Prec = yes(NumChars)
+ ->
+ PrecStr = string__substring(OldStr, 0, NumChars)
;
- make_format_dotnet(Flags, MaybeWidth, MaybePrec, LengthMod,
- Spec)
+ PrecStr = OldStr
+ ),
+ NewStr = justify_string(Flags, Width, PrecStr).
+
+
+ %
+ % Format a float (f)
+ %
+:- func format_float(list(char), maybe(int), maybe(int), float) = string.
+format_float(Flags, Width, Prec, Float) = NewFloat :-
+ %
+ % Determine absolute value of string.
+ %
+ Abs = abs(Float),
+ %
+ % Change precision (default is 6)
+ %
+ AbsStr = convert_float_to_string(Abs),
+ ( Prec = yes(Precision)
+ ->
+ PrecStr = change_precision(Precision, AbsStr)
+ ;
+ PrecStr = change_precision(6, AbsStr)
+ ),
+ %
+ % Do we need to remove the decimal point?
+ %
+ ( \+member('#', Flags),
+ Prec = yes(0)
+ ->
+ PrecStrLen = string__length(PrecStr),
+ PrecModStr = string__substring(PrecStr, 0, PrecStrLen - 1)
+ ;
+ PrecModStr = PrecStr
+ ),
+ %
+ % Do we need to change field width?
+ %
+ ( Width = yes(FieldWidth),
+ FieldWidth > string__length(PrecModStr),
+ member('0', Flags),
+ \+member('-', Flags)
+ ->
+ FieldStr = string__pad_left(PrecModStr, '0', FieldWidth - 1),
+ ZeroPadded = yes
+ ;
+ FieldStr = PrecModStr,
+ ZeroPadded = no
+ ),
+ %
+ % Finishing up ..
+ %
+ ( Float < 0.0 ->
+ SignedStr = string__append("-", FieldStr)
+ ;
+ member('+', Flags) ->
+ SignedStr = string__append("+", FieldStr)
+ ;
+ member(' ', Flags) ->
+ SignedStr = string__append(" ", FieldStr)
+ ;
+ ZeroPadded = yes ->
+ SignedStr = string__append("0", FieldStr)
+ ;
+ SignedStr = FieldStr
+ ),
+ %
+ % Do we need to justify?
+ %
+ NewFloat = justify_string(Flags, Width, SignedStr).
+
+
+ %
+ % Format a scientific number (e,E)
+ %
+:- func format_scientific_number(list(char), maybe(int), maybe(int), float, string) = string.
+format_scientific_number(Flags, Width, Prec, Float, E) = NewFloat :-
+ %
+ % Determine absolute value of string.
+ %
+ Abs = abs(Float),
+ %
+ % Change precision (default is 6)
+ %
+ AbsStr = convert_float_to_string(Abs),
+ ( Prec = yes(Precision)
+ ->
+ PrecStr = change_to_e_notation(AbsStr, Precision, E)
+ ;
+ PrecStr = change_to_e_notation(AbsStr, 6, E)
+ ),
+ %
+ % Do we need to remove the decimal point?
+ %
+ ( \+member('#', Flags),
+ Prec = yes(0)
+ ->
+ BaseAndExponent = string__words(is_decimal_point, PrecStr),
+ list__index0_det(BaseAndExponent, 0, BaseStr),
+ list__index0_det(BaseAndExponent, 1, ExponentStr),
+ PrecModStr = string__append(BaseStr, ExponentStr)
+ ;
+ PrecModStr = PrecStr
+ ),
+ %
+ % Do we need to change field width?
+ %
+ ( Width = yes(FieldWidth),
+ FieldWidth > string__length(PrecModStr),
+ member('0', Flags),
+ \+member('-', Flags)
+ ->
+ FieldStr = string__pad_left(PrecModStr, '0', FieldWidth - 1),
+ ZeroPadded = yes
+ ;
+ FieldStr = PrecModStr,
+ ZeroPadded = no
+ ),
+ %
+ % Finishing up ..
+ %
+ ( Float < 0.0 ->
+ SignedStr = string__append("-", FieldStr)
+ ;
+ member('+', Flags) ->
+ SignedStr = string__append("+", FieldStr)
+ ;
+ member(' ', Flags) ->
+ SignedStr = string__append(" ", FieldStr)
+ ;
+ ZeroPadded = yes ->
+ SignedStr = string__append("0", FieldStr)
+ ;
+ SignedStr = FieldStr
+ ),
+ %
+ % Do we need to justify?
+ %
+ NewFloat = justify_string(Flags, Width, SignedStr).
+
+
+ %
+ % Format a scientific number to a specified number of significant figures (g,G)
+ %
+:- func format_scientific_number_g(list(char), maybe(int), maybe(int), float, string) = string.
+format_scientific_number_g(Flags, Width, Prec, Float, E) = NewFloat :-
+ %
+ % Determine absolute value of string.
+ %
+ Abs = abs(Float),
+ %
+ % Change precision (default is 6)
+ %
+ AbsStr = convert_float_to_string(Abs),
+ ( Prec = yes(Precision)
+ ->
+ (Precision = 0
+ ->
+ PrecStr = change_to_g_notation(AbsStr, 1, E, Flags)
+ ;
+ PrecStr = change_to_g_notation(AbsStr, Precision, E, Flags)
+ )
+ ;
+ PrecStr = change_to_g_notation(AbsStr, 6, E, Flags)
+ ),
+ %
+ % Do we need to change field width?
+ %
+ ( Width = yes(FieldWidth),
+ FieldWidth > string__length(PrecStr),
+ member('0', Flags),
+ \+member('-', Flags)
+ ->
+ FieldStr = string__pad_left(PrecStr, '0', FieldWidth - 1),
+ ZeroPadded = yes
+ ;
+ FieldStr = PrecStr,
+ ZeroPadded = no
+ ),
+ %
+ % Finishing up ..
+ %
+ ( Float < 0.0 ->
+ SignedStr = string__append("-", FieldStr)
+ ;
+ member('+', Flags) ->
+ SignedStr = string__append("+", FieldStr)
+ ;
+ member(' ', Flags) ->
+ SignedStr = string__append(" ", FieldStr)
+ ;
+ ZeroPadded = yes ->
+ SignedStr = string__append("0", FieldStr)
+ ;
+ SignedStr = FieldStr
+ ),
+ %
+ % Do we need to justify?
+ %
+ NewFloat = justify_string(Flags, Width, SignedStr).
+
+
+% ------- UTILITY FUNCTIONS.
+
+
+ %
+ % Given a floating point number, this function calculates the size of the exponent
+ % needed to represent the float in scientific notation.
+ %
+:- func size_of_required_exponent(string, int) = int.
+size_of_required_exponent(Float, Prec) = Exponent :-
+ UnsafeExponent = decimal_pos(Float),
+ UnsafeBase = calculate_base_unsafe(Float, Prec),
+ %
+ % Is mantissa one digit long?
+ %
+ MantAndFrac = string__words(is_decimal_point, UnsafeBase),
+ list__index0_det(MantAndFrac, 0, MantissaStr),
+ ( string__length(MantissaStr) > 1
+ ->
+ % we will need need to move decimal pt one place to the left: therefore,
+ % increment exponent.
+ Exponent = UnsafeExponent + 1
+ ;
+ Exponent = UnsafeExponent
).
+ %
+ % Given a string representing a floating point number, function returns a string with
+ % all trailing zeros removed.
+ %
+:- func remove_trailing_zeros(string) = string.
+remove_trailing_zeros(Float) = TrimmedFloat :-
+ FloatCharList = string__to_char_list(Float),
+ FloatCharListRev = list__reverse(FloatCharList),
+ TrimmedFloatRevCharList = remove_zeros(FloatCharListRev),
+ TrimmedFloatCharList = list__reverse(TrimmedFloatRevCharList),
+ TrimmedFloat = string__from_char_list(TrimmedFloatCharList).
-:- pred using_sprintf is semidet.
+ %
+ % Given a char list, this function removes all leading zeros, including decimal point,
+ % if need be.
+ %
+:- func remove_zeros(list(char)) = list(char).
+remove_zeros(CharNum) = TrimmedNum :-
+ ( CharNum = ['0'|Rest]
+ ->
+ TrimmedNum = remove_zeros(Rest)
+ ;
+ CharNum = ['.'|Rest]
+ ->
+ TrimmedNum = Rest
+ ;
+ TrimmedNum = CharNum
+ ).
-:- pragma foreign_proc("C", using_sprintf,
- [will_not_call_mercury, promise_pure, thread_safe], "
- SUCCESS_INDICATOR = TRUE;
-").
-:- pragma foreign_proc("MC++", using_sprintf,
- [will_not_call_mercury, promise_pure, thread_safe], "
- SUCCESS_INDICATOR = FALSE;
-").
-
- % Construct a format string suitable to passing to sprintf.
-:- func make_format_sprintf(list(char), maybe(list(char)),
- maybe(list(char)), string, string) = string.
+ %
+ % Converts a floating point number to a specified number of standard figures.
+ % The style used depends on the value converted; style e (or E) is used only if the
+ % exponent resulting from such a conversion is less than -4 or greater than or equal
+ % to the precision. Trailing zeros are removed from the fractional portion of the
+ % result unless the # flag is specified: a decimal-point character appears only if it
+ % is followed by a digit.
+ %
+:- func change_to_g_notation(string, int, string, list(char)) = string.
+change_to_g_notation(Float, Prec, E, Flags) = FormattedFloat :-
+ Exponent = size_of_required_exponent(Float, Prec),
+ ( Exponent >= -4, Exponent < Prec
+ ->
+ % Float will be represented normally.
+ % -----------------------------------
+ % Need to calculate precision to pass to the change_precision
+ % function, because the current precision represents significant
+ % figures, not decimal places.
+ %
+ % now change float's precision.
+ %
+ ( Exponent =< 0
+ ->
+ %
+ % deal with floats such as 0.00000000xyz
+ %
+ DecimalPos = decimal_pos(Float),
+ FormattedFloat0 = change_precision(abs(DecimalPos)-1+Prec, Float)
+ ;
+ %
+ % deal with floats such as ddddddd.mmmmmmmm
+ %
+ ScientificFloat = change_to_e_notation(Float, Prec - 1, "e"),
+ BaseAndExponent = string__words(is_exponent, ScientificFloat),
+ list__index0_det(BaseAndExponent, 0, BaseStr),
+ list__index0_det(BaseAndExponent, 1, ExponentStr),
+ Exp = string__det_to_int(ExponentStr),
+ MantAndFrac = string__words(is_decimal_point, BaseStr),
+ list__index0_det(MantAndFrac, 0, MantissaStr),
+ list__index0_det(MantAndFrac, 1, FractionStr),
+ RestMantissaStr = string__substring(FractionStr, 0, Exp),
+ NewFraction = string__substring(FractionStr, Exp, Prec - Exp -1),
+ FormattedFloat0 = MantissaStr ++ RestMantissaStr ++ "." ++ NewFraction
+ ),
+ %
+ % Do we remove trailing zeros?
+ %
+ ( member('#', Flags)
+ ->
+ FormattedFloat = FormattedFloat0
+ ;
+ FormattedFloat = remove_trailing_zeros(FormattedFloat0)
+ )
+ ;
+ % Float will be represented in scientific notation.
+ % -------------------------------------------------
+ %
+ UncheckedFloat = change_to_e_notation(Float, Prec - 1, E),
+ %
+ % Do we need to remove trailing zeros?
+ %
+ ( member('#', Flags)
+ ->
+ FormattedFloat = UncheckedFloat
+ ;
+ BaseAndExponent = string__words(is_exponent, UncheckedFloat),
+ list__index0_det(BaseAndExponent, 0, BaseStr),
+ list__index0_det(BaseAndExponent, 1, ExponentStr),
+ NewBaseStr = remove_trailing_zeros(BaseStr),
+ FormattedFloat = NewBaseStr ++ E ++ ExponentStr
+ )
+ ).
-make_format_sprintf(Flags, MaybeWidth, MaybePrec, LengthMod, Spec) = String :-
- (
- MaybeWidth = yes(Width)
+
+
+ %
+ % convert floating point notation to scientific notation.
+ %
+:- func change_to_e_notation(string, int, string) = string.
+change_to_e_notation(Float, Prec, E) = ScientificFloat :-
+ UnsafeExponent = decimal_pos(Float),
+ UnsafeBase = calculate_base_unsafe(Float, Prec),
+ %
+ % Is mantissa one digit long?
+ %
+ MantAndFrac = string__words(is_decimal_point, UnsafeBase),
+ list__index0_det(MantAndFrac, 0, MantissaStr),
+ ( string__length(MantissaStr) > 1
+ ->
+ % need to append 0, to fix the problem of having no numbers after the
+ % decimal point.
+ SafeBase = calculate_base_unsafe(string__append(UnsafeBase, "0"), Prec),
+ SafeExponent = UnsafeExponent + 1
;
- MaybeWidth = no,
- Width = []
+ SafeBase = UnsafeBase,
+ SafeExponent = UnsafeExponent
),
- (
- MaybePrec = yes(Prec0),
- Prec = ['.' | Prec0]
+ %
+ % Creating exponent.
+ %
+ ( SafeExponent >= 0
+ ->
+ (SafeExponent < 10
+ ->
+ ExponentStr = string__append_list([E, "+0", string__int_to_string(SafeExponent)])
+ ;
+ ExponentStr = string__append_list([E, "+", string__int_to_string(SafeExponent)])
+ )
;
- MaybePrec = no,
- Prec = []
+ (SafeExponent > -10
+ ->
+ ExponentStr = string__append_list([E, "-0", string__int_to_string(int__abs(SafeExponent))])
+ ;
+ ExponentStr = string__append(E, string__int_to_string(SafeExponent))
+ )
),
- String = string__append_list(["%", from_char_list(Flags),
- from_char_list(Width),
- from_char_list(Prec), LengthMod, Spec]).
+ ScientificFloat = string__append(SafeBase, ExponentStr).
- % Construct a format string suitable to passing to .NET's formatting
- % functions.
- % XXX this code is not yet complete. We need to do a lot more work
- % to make this work perfectly.
-:- func make_format_dotnet(list(char), maybe(list(char)),
- maybe(list(char)), string, string) = string.
-make_format_dotnet(_Flags, MaybeWidth, MaybePrec, _LengthMod, Spec0) = String :-
- (
- MaybeWidth = yes(Width0),
- Width = [',' | Width0]
+ %
+ % Representing a floating point number in scientific notation requires a base
+ % and an exponent. This function returns the base. But it is unsafe, since
+ % particular input result in the base having a mantissa with more than one digit.
+ % Therefore, the calling function must check for this problem.
+ %
+:- func calculate_base_unsafe(string, int) = string.
+calculate_base_unsafe(Float, Prec) = Exp :-
+ Place = decimal_pos(Float),
+ MantAndFrac = string__words(is_decimal_point, Float),
+ list__index0_det(MantAndFrac, 0, MantissaStr),
+ list__index0_det(MantAndFrac, 1, FractionStr),
+ ( Place < 0
+ ->
+ DecimalPos = abs(Place),
+ PaddedMantissaStr = string__substring(FractionStr, 0, DecimalPos),
+ %
+ % get rid of superfluous zeros.
+ %
+ MantissaInt = string__det_to_int(PaddedMantissaStr),
+ ExpMantissaStr = string__int_to_string(MantissaInt),
+ %
+ % create fractional part
+ %
+ PaddedFractionStr = string__pad_right(FractionStr, '0', Prec+1),
+ ExpFractionStr = string__substring(PaddedFractionStr, DecimalPos, Prec+1)
+ ; Place > 0
+ ->
+ ExpMantissaStr = string__substring(MantissaStr, 0, 1),
+ FirstHalfOfFractionStr = string__substring(MantissaStr, 1, Place),
+ ExpFractionStr = string__append(FirstHalfOfFractionStr, FractionStr)
;
- MaybeWidth = no,
- Width = []
+ ExpMantissaStr = MantissaStr,
+ ExpFractionStr = FractionStr
),
- (
- MaybePrec = yes(Prec)
+ MantissaAndPoint = string__append(ExpMantissaStr, "."),
+ UnroundedExpStr = string__append(MantissaAndPoint, ExpFractionStr),
+ Exp = change_precision(Prec, UnroundedExpStr).
+
+
+ %
+ % Determine the location of the decimal point in the string that represents a
+ % floating point number.
+ %
+:- func decimal_pos(string) = int.
+decimal_pos(Float) = Pos :-
+ MantAndFrac = string__words(is_decimal_point, Float),
+ list__index0_det(MantAndFrac, 0, MantissaStr),
+ NumZeros = string__length(MantissaStr) - 1,
+ Pos = find_non_zero_pos(string__to_char_list(Float), NumZeros).
+
+
+ %
+ % Given a list of chars representing a floating point number, function determines the
+ % the first position containing a non-zero digit. Positions after the decimal
+ % point are negative, and those before the decimal point are positive.
+ %
+:- func find_non_zero_pos(list(char), int) = int.
+find_non_zero_pos( Xs, CurrentPos) = ActualPos :-
+ ( Xs = [Y|Ys]
+ ->
+ ( is_decimal_point(Y)
+ ->
+ ActualPos = find_non_zero_pos(Ys, CurrentPos)
+ ; Y = '0'
+ ->
+ ActualPos = find_non_zero_pos(Ys, CurrentPos - 1)
+ ;
+ ActualPos = CurrentPos
+ )
+ ;
+ ActualPos = 0
+ ).
+
+
+ %
+ % Change the precision of a float to a specified number of decimal places.
+ %
+ % n.b. OldFloat must be positive for this function to work.
+ %
+:- func change_precision(int, string) = string.
+change_precision(Prec, OldFloat) = NewFloat :-
+ MantAndFrac = string__words(is_decimal_point, OldFloat),
+ list__index0_det(MantAndFrac, 0, MantissaStr),
+ list__index0_det(MantAndFrac, 1, FractionStr),
+ FracStrLen = string__length(FractionStr),
+ ( Prec > FracStrLen
+ ->
+ PrecFracStr = string__pad_right(FractionStr, '0', Prec),
+ PrecMantissaStr = MantissaStr
+ ;
+ Prec < FracStrLen
+ ->
+ UnroundedFrac = string__substring(FractionStr, 0, Prec),
+ NextDigit = string__index_det(FractionStr, Prec),
+ ( UnroundedFrac \= "",
+ (char__to_int(NextDigit) - char__to_int('0')) >= 5
+ ->
+ NewPrecFrac = string__det_to_int(UnroundedFrac) + 1,
+ NewPrecFracStrNotOK = string__int_to_string(NewPrecFrac),
+ NewPrecFracStr = string__pad_left(NewPrecFracStrNotOK, '0', Prec),
+ ( string__length(NewPrecFracStr) > string__length(UnroundedFrac)
+ ->
+ PrecFracStr = string__substring(NewPrecFracStr, 1, Prec),
+ PrecMantissaInt = string__det_to_int(MantissaStr) + 1,
+ PrecMantissaStr = string__int_to_string(PrecMantissaInt)
+ ;
+ PrecFracStr = NewPrecFracStr,
+ PrecMantissaStr = MantissaStr
+ )
+
+ ; UnroundedFrac = "",
+ (char__to_int(NextDigit) - char__to_int('0')) >= 5
+ ->
+ PrecMantissaInt = string__det_to_int(MantissaStr) + 1,
+ PrecMantissaStr = string__int_to_string(PrecMantissaInt),
+ PrecFracStr = ""
+ ;
+ PrecFracStr = UnroundedFrac,
+ PrecMantissaStr = MantissaStr
+ )
;
- MaybePrec = no,
- Prec = []
+ PrecFracStr = FractionStr,
+ PrecMantissaStr = MantissaStr
),
- ( Spec0 = "i" -> Spec = "d"
- ; Spec0 = "f" -> Spec = "e"
- ; Spec = Spec0
- ),
- String = string__append_list([
- "{0",
- from_char_list(Width),
- ":",
- Spec,
- from_char_list(Prec),
-% LengthMod,
-% from_char_list(Flags),
- "}"]).
+ HalfNewFloat = string__append(PrecMantissaStr, "."),
+ NewFloat = string__append(HalfNewFloat, PrecFracStr).
+:- pred is_decimal_point(char :: in) is semidet.
+is_decimal_point('.').
-:- func int_length_modifer = string.
-:- pragma foreign_proc("C",
- int_length_modifer = (LengthModifier::out),
- [will_not_call_mercury, promise_pure, thread_safe], "{
- MR_make_aligned_string(LengthModifier,
- (MR_String) (MR_Word) MR_INTEGER_LENGTH_MODIFIER);
-}").
+:- pred is_exponent(char :: in) is semidet.
+is_exponent('e').
-:- pragma foreign_proc("C#",
- int_length_modifer = (LengthModifier::out),
- [will_not_call_mercury, promise_pure, thread_safe], "{
- LengthModifier = """";
-}").
+ %
+ % Unlike the standard library function, this function converts a float to a string
+ % without resorting to scientific notation.
+ %
+:- func convert_float_to_string(float) = string.
+convert_float_to_string(Float) = String :-
+ FloatStr = string__float_to_string(Float),
+ %
+ % check for scientific representation.
+ %
+ ( string__contains_char(FloatStr, 'e')
+ ->
+ FloatAndExp = string__words(is_exponent, FloatStr),
+ list__index0_det(FloatAndExp, 0, FloatPtStr),
+ list__index0_det(FloatAndExp, 1, ExpStr),
+ %
+ % extract mantissa and fractional part
+ %
+ MantAndFrac = string__words(is_decimal_point, FloatPtStr),
+ list__index0_det(MantAndFrac, 0, MantissaStr),
+ list__index0_det(MantAndFrac, 1, FractionStr),
+ %
+ % what is the exponent?
+ %
+ ExpInt = string__det_to_int(ExpStr),
+ ( ExpInt >= 0
+ ->
+ %
+ % move decimal pt to the right.
+ %
+ ExtraDigits = ExpInt,
+ PaddedFracStr = string__pad_right(FractionStr, '0', ExtraDigits),
+ string__split(PaddedFracStr, ExtraDigits, MantissaRest, NewFraction),
+ NewMantissa = append(MantissaStr, MantissaRest),
+ MantAndPoint = append(NewMantissa, "."),
+ ( NewFraction = ""
+ ->
+ String = append(MantAndPoint, "0")
+ ;
+ String = append(MantAndPoint, NewFraction)
+ )
+ ;
+ %
+ % move decimal pt to the left.
+ %
+ ExtraDigits = abs(ExpInt),
+ PaddedMantissaStr = string__pad_left(MantissaStr, '0', ExtraDigits),
+ string__split(PaddedMantissaStr, length(PaddedMantissaStr) - ExtraDigits ,
+ NewMantissa, FractionRest),
+ (NewMantissa = ""
+ ->
+ MantAndPoint = "0."
+ ;
+ MantAndPoint = string__append(NewMantissa, ".")
+ ),
+ String = append(MantAndPoint, append(FractionRest, FractionStr))
+ )
+ ;
+ String = FloatStr
+ ).
+
+
+ %
+ % Convert an integer to an octal string.
+ %
+:- func to_octal(integer) = string.
+to_octal(Num) = NumStr :-
+ ( Num > integer(0)
+ ->
+ Rest = to_octal(Num // integer(8)),
+ Rem = Num rem integer(8),
+ RemStr = integer__to_string(Rem),
+ NumStr = append(Rest, RemStr)
+ ;
+ NumStr = ""
+ ).
+
+
+ %
+ % Convert an integer to a hexadecimal string using a-f.
+ %
+:- func to_hex(integer) = string.
+to_hex(Num) = NumStr :-
+ ( Num > integer(0)
+ ->
+ Rest = to_hex( Num // integer(16)),
+ Rem = Num rem integer(16),
+ RemStr = get_hex_int(Rem),
+ NumStr = append(Rest, RemStr)
+ ;
+ NumStr = ""
+ ).
+
+
+ %
+ % Convert an integer to a hexadecimal string using A-F.
+ %
+:- func to_capital_hex(integer) = string.
+to_capital_hex(Num) = NumStr :-
+ ( Num > integer(0)
+ ->
+ Rest = to_capital_hex( Num//integer(16)),
+ Rem = Num rem integer(16),
+ RemStr = get_capital_hex_int(Rem),
+ NumStr = append(Rest, RemStr)
+ ;
+ NumStr = ""
+ ).
+
+
+ %
+ % Given a decimal integer, return the hexadecimal equivalent (using a-f).
+ %
+:- func get_hex_int(integer) = string.
+get_hex_int(Int) = HexStr :-
+ ( Int < integer(10) -> HexStr = integer__to_string(Int)
+ ;
+ Int = integer(10) -> HexStr = "a"
+ ;
+ Int = integer(11) -> HexStr = "b"
+ ;
+ Int = integer(12) -> HexStr = "c"
+ ;
+ Int = integer(13) -> HexStr = "d"
+ ;
+ Int = integer(14) -> HexStr = "e"
+ ;
+ HexStr = "f"
+ ).
+
+ %
+ % Convert an integer to a hexadecimal string using A-F.
+ %
+:- func get_capital_hex_int(integer) = string.
+get_capital_hex_int(Int) = HexStr :-
+ ( Int < integer(10) -> HexStr = integer__to_string(Int)
+ ;
+ Int = integer(10) -> HexStr = "A"
+ ;
+ Int = integer(11) -> HexStr = "B"
+ ;
+ Int = integer(12) -> HexStr = "C"
+ ;
+ Int = integer(13) -> HexStr = "D"
+ ;
+ Int = integer(14) -> HexStr = "E"
+ ;
+ HexStr = "F"
+ ).
+
+
+
+ %
+ % Given a string and set of flags, this function will either left or right justify
+ % the string.
+ %
+:- func justify_string(list(char), maybe(int), string) = string.
+justify_string(Flags, Width, OldStr) = JustifiedStr :-
+ ( Width = yes(FWidth),
+ FWidth > string__length(OldStr)
+ ->
+ ( member('-', Flags) % do we left (or right) justify?
+ ->
+ JustifiedStr = string__pad_right(OldStr, ' ', FWidth)
+ ;
+ JustifiedStr = string__pad_left(OldStr, ' ', FWidth)
+ )
+ ;
+ JustifiedStr = OldStr
+ ).
- % Create a string from a float using the format string.
- % Note it is the responsibility of the caller to ensure that the
- % format string is valid.
-:- func format_float(string, float) = string.
-:- pragma foreign_proc("C",
- format_float(FormatStr::in, Val::in) = (Str::out),
- [will_not_call_mercury, promise_pure, thread_safe], "{
- MR_save_transient_hp();
- Str = MR_make_string(MR_PROC_LABEL, FormatStr, (double) Val);
- MR_restore_transient_hp();
-}").
-:- pragma foreign_proc("C#",
- format_float(FormatStr::in, Val::in) = (Str::out),
- [will_not_call_mercury, promise_pure, thread_safe], "{
- Str = System.String.Format(FormatStr, Val);
-}").
- % Create a string from a int using the format string.
- % Note it is the responsibility of the caller to ensure that the
- % format string is valid.
-:- func format_int(string, int) = string.
-:- pragma foreign_proc("C",
- format_int(FormatStr::in, Val::in) = (Str::out),
- [will_not_call_mercury, promise_pure, thread_safe], "{
- MR_save_transient_hp();
- Str = MR_make_string(MR_PROC_LABEL, FormatStr, Val);
- MR_restore_transient_hp();
-}").
-:- pragma foreign_proc("C#",
- format_int(FormatStr::in, Val::in) = (Str::out),
- [will_not_call_mercury, promise_pure, thread_safe], "{
- Str = System.String.Format(FormatStr, Val);
-}").
- % Create a string from a string using the format string.
- % Note it is the responsibility of the caller to ensure that the
- % format string is valid.
-:- func format_string(string, string) = string.
-:- pragma foreign_proc("C",
- format_string(FormatStr::in, Val::in) = (Str::out),
- [will_not_call_mercury, promise_pure, thread_safe], "{
- Str = MR_make_string(MR_PROC_LABEL, FormatStr, Val);
-}").
-:- pragma foreign_proc("C#",
- format_string(FormatStr::in, Val::in) = (Str::out),
- [will_not_call_mercury, promise_pure, thread_safe], "{
- Str = System.String.Format(FormatStr, Val);
-}").
- % Create a string from a char using the format string.
- % Note it is the responsibility of the caller to ensure that the
- % format string is valid.
-:- func format_char(string, char) = string.
-:- pragma foreign_proc("C",
- format_char(FormatStr::in, Val::in) = (Str::out),
- [will_not_call_mercury, promise_pure, thread_safe], "{
- MR_save_transient_hp();
- Str = MR_make_string(MR_PROC_LABEL, FormatStr, Val);
- MR_restore_transient_hp();
-}").
-:- pragma foreign_proc("C#",
- format_char(FormatStr::in, Val::in) = (Str::out),
- [will_not_call_mercury, promise_pure, thread_safe], "{
- Str = System.String.Format(FormatStr, Val);
-}").
%-----------------------------------------------------------------------------%
@@ -2539,7 +3223,7 @@
preceding_boundary(SepP, String, I - 1)
).
-% ---------------------------------------------------------------------------- %
+% ---------------------------------------------------------------------------- v%
S1 ++ S2 = string__append(S1, S2).
--------------------------------------------------------------------------
mercury-reviews mailing list
post: mercury-reviews at cs.mu.oz.au
administrative address: owner-mercury-reviews at cs.mu.oz.au
unsubscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: unsubscribe
subscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: subscribe
--------------------------------------------------------------------------
More information about the reviews
mailing list