diff --git a/library/MODULES_UNDOC b/library/MODULES_UNDOC index 2867ab0ab..7e21a50d9 100644 --- a/library/MODULES_UNDOC +++ b/library/MODULES_UNDOC @@ -15,8 +15,10 @@ robdd.m rtti_implementation.m stm_builtin.m string.format.m +string.from_int.m string.parse_runtime.m string.parse_util.m +string.to_int.m string.to_string.m table_builtin.m term_size_prof_builtin.m diff --git a/library/library.m b/library/library.m index 9c240bd7f..d21251b8c 100644 --- a/library/library.m +++ b/library/library.m @@ -727,8 +727,10 @@ stdlib_module_doc_undoc("stream.string_writer", doc). stdlib_module_doc_undoc("string", doc). stdlib_module_doc_undoc("string.builder", doc). stdlib_module_doc_undoc("string.format", undoc). +stdlib_module_doc_undoc("string.from_int", undoc). stdlib_module_doc_undoc("string.parse_runtime", undoc). stdlib_module_doc_undoc("string.parse_util", undoc). +stdlib_module_doc_undoc("string.to_int", undoc). stdlib_module_doc_undoc("string.to_string", undoc). stdlib_module_doc_undoc("table_builtin", undoc). stdlib_module_doc_undoc("table_statistics", doc). diff --git a/library/string.from_int.m b/library/string.from_int.m new file mode 100644 index 000000000..9c3f0f068 --- /dev/null +++ b/library/string.from_int.m @@ -0,0 +1,98 @@ +%---------------------------------------------------------------------------% +% vim: ts=4 sw=4 et ft=mercury +%---------------------------------------------------------------------------% +% Copyright (C) 1994-2012 The University of Melbourne. +% Copyright (C) 2013-2026 The Mercury team. +% This file is distributed under the terms specified in COPYING.LIB. +%---------------------------------------------------------------------------% +% +% File: string.from_int.m. +% Main author: juliensf. +% +% This module includes the non-interface parts of operations +% that convert from integers to strings. +% +%---------------------------------------------------------------------------% + +:- module string.from_int. +:- interface. + +:- import_module char. +:- import_module list. + + % int_to_base_string_loop(NegN, Base, !RevChars): + % + % Convert the integer NegN, which must be strictly negative, + % to its base Base string representation. Add the characters of + % that string representation in reverse order in front of !.RevChars. + % +:- pred int_to_base_string_loop(int::in, int::in, + list(char)::in, list(char)::out) is det. + + % int_to_base_string_group_loop(NegN, Base, Curr, GroupLength, Sep, Str): + + % int_to_base_string_group_loop/6 has almost the same job as + % int_to_base_string_loop/3 above, so any changes here might also + % need to be applied to int_to_base_string_loop/3. + % + % The difference between the two predicates is that this one + % also adds the separator Sep every GroupLength digits. + % Curr is how many digits have been processed since the last separator + % was inserted. This is an internal-use counter; our caller in string.m + % should always pass zero. + % +:- pred int_to_base_string_group_loop(int::in, int::in, int::in, int::in, + string::in, string::uo) is det. + +%---------------------------------------------------------------------------% + +:- implementation. + +:- import_module int. + +%---------------------------------------------------------------------------% + +int_to_base_string_loop(NegN, Base, !RevChars) :- + % int_to_base_string_loop/3 is almost identical to + % int_to_base_string_group_loop/6 below so any changes here might + % also need to be applied to int_to_base_string_group_loop/3. + ( if NegN > -Base then + N = -NegN, + DigitChar = char.det_base_int_to_digit(Base, N), + !:RevChars = [DigitChar | !.RevChars] + else + NegN1 = NegN // Base, + N10 = (NegN1 * Base) - NegN, + DigitChar = char.det_base_int_to_digit(Base, N10), + int_to_base_string_loop(NegN1, Base, !RevChars), + !:RevChars = [DigitChar | !.RevChars] + ). + +%---------------------------------------------------------------------------% + +int_to_base_string_group_loop(NegN, Base, Curr, GroupLength, Sep, Str) :- + ( if + Curr = GroupLength, + GroupLength > 0 + then + int_to_base_string_group_loop(NegN, Base, 0, GroupLength, Sep, Str1), + string.append(Str1, Sep, Str) + else + ( if NegN > -Base then + N = -NegN, + DigitChar = char.det_base_int_to_digit(Base, N), + string.char_to_string(DigitChar, Str) + else + NegN1 = NegN // Base, + N10 = (NegN1 * Base) - NegN, + DigitChar = char.det_base_int_to_digit(Base, N10), + string.char_to_string(DigitChar, DigitString), + int_to_base_string_group_loop(NegN1, Base, Curr + 1, + GroupLength, Sep, Str1), + string.append(Str1, DigitString, Str) + ) + ). + +%---------------------------------------------------------------------------% +:- end_module string.from_int. +%---------------------------------------------------------------------------% diff --git a/library/string.m b/library/string.m index 89aa290e8..b23891855 100644 --- a/library/string.m +++ b/library/string.m @@ -1929,6 +1929,8 @@ :- implementation. :- include_module parse_runtime. +:- include_module from_int. +:- include_module to_int. :- include_module to_string. :- import_module bool. @@ -1937,8 +1939,9 @@ :- import_module pair. :- import_module require. :- import_module string.format. +:- import_module string.from_int. +:- import_module string.to_int. :- import_module string.to_string. -:- import_module uint. :- import_module uint8. %---------------------------------------------------------------------------% @@ -5705,237 +5708,6 @@ det_base_string_to_int(Base, S) = N :- %---------------------% -:- type int_sign - ---> positive - ; negative. - -:- pred do_base_string_to_int(int::in, string::in, int::out) is semidet. - -do_base_string_to_int(Base, String, Int) :- - string.index(String, 0, Char), - End = string.count_code_units(String), - ( if - ( Char = ('-'), Sign0 = negative - ; Char = ('+'), Sign0 = positive - ) - then - Sign = Sign0, - % Start at the first digit, which *should* be just after the sign. - End > 1, - Start = 1 - else - Sign = positive, - Start = 0 - ), - - % Do not include the sign bit, if any, in our initial digit count. - NumStringDigits = End - Start, - - % The divisions below are all safe since our callers set Base - % to be in 2..36. - ( - Sign = positive, - ( if can_use_to_int_fast_path(Base, NumStringDigits) then - do_unsafe_base_string_to_positive_int_loop(Base, String, - Start, End, 0, Int) - else - CutOff = max_int `unchecked_quotient` Base, - CutLimit = max_int `unchecked_rem` Base, - do_base_string_to_positive_int_loop(Base, CutOff, CutLimit, String, - Start, End, 0, Int) - ) - ; - Sign = negative, - ( if can_use_to_int_fast_path(Base, NumStringDigits) then - do_unsafe_base_string_to_negative_int_loop(Base, String, - Start, End, 0, Int) - else - CutOff = min_int `unchecked_quotient` Base, - CutLimit = -(min_int `unchecked_rem` Base), - do_base_string_to_negative_int_loop(Base, CutOff, CutLimit, String, - Start, End, 0, Int) - ) - ). - -%---------------------% - - % can_use_to_int_fast_path(Base, NumDigits): - % - % Can we safely use the fast path string-to-int conversion for a string of - % base Base digits of length NumDigits? - % -:- pred can_use_to_int_fast_path(int::in, int::in) is semidet. - -can_use_to_int_fast_path(Base, NumDigits) :- - WordSize = bits_per_int, - ( - WordSize = 32, - safe_num_digits_for_base_i32_u32_i64_u64(Base, SafeDigits, _, _, _) - ; - WordSize = 64, - safe_num_digits_for_base_i32_u32_i64_u64(Base, _, _, SafeDigits, _) - ), - NumDigits =< SafeDigits. - -%---------------------% - - % do_base_string_to_positive_int_loop(Base, CutOff, CutLimit, String, - % CurOffset, EndOffset, !Int): - % - % Convert the base Base digits of String between CurOffset and EndOffset - % into an int, reading a digit M from the string in each iteration and - % accumulating the result in !Int. Fail if the value being accumulated - % would exceed max_int. - % - % We must detect the overflow *before* it happens. Computing - % (Base * !.Int) + M and then testing the result does not work, - % because the multiplication may overflow by more than the range of - % an int. In that case, the wrapped-around result is again greater - % than !.Int, and so is indistinguishable from a result that did not - % overflow. - % - % Requiring that !.Int =< (max_int - M) // Base be true at each iteration - % of the loop does detect overflow, but at the cost of having a division in - % each iteration. - % - % Instead, we can hoist the division out of the loop body by observing that - % the above check depends on the digit M only through a comparison that - % can be split into cases. Specifically, we can write max_int as: - % - % max_int = (Base * CutOff) + CutLimit - % - % CutOff and CutLimit are invariant and our caller will compute them as: - % - % CutOff = max_int // Base - % CutLimit = max_int rem Base (implying 0 =< CutLimit < Base) - % - % Given these, (Base * !.Int) + M does not exceed max_int if and only - % if either: - % - % - !.Int < CutOff, in which case, since M < Base, - % - % (Base * !.Int) + M =< (Base * (CutOff - 1)) + (Base - 1) - % = (Base * CutOff) - 1 - % =< max_int - % - % whatever the digit M is; or - % - % - !.Int = CutOff and M =< CutLimit, in which case - % - % (Base * !.Int) + M =< (Base * CutOff) + CutLimit = max_int. - % - % If !.Int > CutOff, then - % - % (Base * !.Int) >= (Base * CutOff) + Base, - % - % which exceeds max_int whatever the digit is. - % -:- pred do_base_string_to_positive_int_loop(int::in, int::in, int::in, - string::in, int::in, int::in, int::in, int::out) is semidet. - -do_base_string_to_positive_int_loop(Base, CutOff, CutLimit, String, - CurOffset, EndOffset, !Int) :- - ( if CurOffset < EndOffset then - unsafe_index_next(String, CurOffset, NextOffset, Char), - char.unsafe_base_digit_to_int(Base, Char, M), - % Fail if (Base * !.Int) + M would exceed max_int. - ( !.Int < CutOff - ; !.Int = CutOff, M =< CutLimit - ), - !:Int = (Base * !.Int) + M, - do_base_string_to_positive_int_loop(Base, CutOff, CutLimit, String, - NextOffset, EndOffset, !Int) - else - true - ). - - % do_base_string_to_negative_int_loop(Base, CutOff, CutLimit, String, - % CurOffset, EndOffset, !Int): - % - % This predicate is similar to do_base_string_to_positive_int_loop above, - % but accumulates a negative value, and fail if it would be less than - % min_int. Here our caller gives us - % - % CutOff = min_int // Base - % CutLimit = -(min_int rem Base) - % - % so that min_int = (Base * CutOff) - CutLimit, where CutLimit is - % again in 0 .. (Base - 1). The test mirrors the positive case: the - % step is safe if !.Int > CutOff, whatever the digit M is, or if - % !.Int = CutOff and M =< CutLimit. - % - % Note that we must be the truncating quotient and remainder. - % For a negative dividend, truncation rounds towards zero, which makes - % CutOff the ceiling of the exact quotient min_int / Base; that is the - % tight bound on !.Int. Flooring division yields a CutOff one lower - % whenever Base does not divide min_int exactly, and the test - % !.Int > CutOff would then accept an accumulator value whose next step - % overflows. With 32-bit ints and Base = 10, for example, it would - % convert "-2147483649" to 2147483647 instead of failing. - % - % Note also that we cannot avoid the issue by accumulating a - % positive value and negating it at the end, since the magnitude of - % min_int is not representable as a positive int. - % -:- pred do_base_string_to_negative_int_loop(int::in, int::in, int::in, - string::in, int::in, int::in, int::in, int::out) is semidet. - -do_base_string_to_negative_int_loop(Base, CutOff, CutLimit, String, - CurOffset, EndOffset, !Int) :- - ( if CurOffset < EndOffset then - unsafe_index_next(String, CurOffset, NextOffset, Char), - char.unsafe_base_digit_to_int(Base, Char, M), - % Fail if (Base * !.Int) - M would be less than min_int. - ( !.Int > CutOff - ; !.Int = CutOff, M =< CutLimit - ), - !:Int = (Base * !.Int) - M, - do_base_string_to_negative_int_loop(Base, CutOff, CutLimit, String, - NextOffset, EndOffset, !Int) - else - true - ). - -%---------------------% - - % A version of do_base_string_to_positive_int_loop that omits the the - % overflow check. This is faster, but can only be used safely when the - % number of digits in the string is below that which potentially overflow. - % -:- pred do_unsafe_base_string_to_positive_int_loop(int::in, string::in, - int::in, int::in, int::in, int::out) is semidet. - -do_unsafe_base_string_to_positive_int_loop(Base, String, CurOffset, - EndOffset, !Int) :- - ( if CurOffset < EndOffset then - unsafe_index_next(String, CurOffset, NextOffset, Char), - char.unsafe_base_digit_to_int(Base, Char, M), - !:Int = (Base * !.Int) + M, - do_unsafe_base_string_to_positive_int_loop(Base, String, NextOffset, - EndOffset, !Int) - else - true - ). - - % As above, but for the negative case. - % -:- pred do_unsafe_base_string_to_negative_int_loop(int::in, string::in, - int::in, int::in, int::in, int::out) is semidet. - -do_unsafe_base_string_to_negative_int_loop(Base, String, CurOffset, - EndOffset, !Int) :- - ( if CurOffset < EndOffset then - unsafe_index_next(String, CurOffset, NextOffset, Char), - char.unsafe_base_digit_to_int(Base, Char, M), - !:Int = (Base * !.Int) - M, - do_unsafe_base_string_to_negative_int_loop(Base, String, - NextOffset, EndOffset, !Int) - else - true - ). - -%---------------------% - to_uint(String, UInt) :- do_base_string_to_uint(10, String, UInt). @@ -5957,162 +5729,7 @@ det_base_string_to_uint(Base, S) = N :- unexpected($pred, "conversion failed") ). -%---------------------% - -:- pred do_base_string_to_uint(int::in, string::in, uint::out) is semidet. - -do_base_string_to_uint(Base, String, UInt) :- - End = string.count_code_units(String), - End > 0, % Fail if we have the empty string. - UBase = uint.cast_from_int(Base), - % The divisions below are safe since our callers set Base - % to be in 2..36. - ( if can_use_to_uint_fast_path(Base, End) then - do_unsafe_base_string_to_uint_loop(UBase, Base, String, 0, End, - 0u, UInt) - else - CutOff = max_uint `unchecked_quotient` UBase, - CutLimit = max_uint `unchecked_rem` UBase, - do_base_string_to_uint_loop(UBase, Base, CutOff, CutLimit, String, - 0, End, 0u, UInt) - ). - -%---------------------% - - % can_use_to_uint_fast_path(Base, NumDigits): - % - % Can we safely use the fast path string-to-uint conversion for a string of - % base Base digits of length NumDigits? - % -:- pred can_use_to_uint_fast_path(int::in, int::in) is semidet. - -can_use_to_uint_fast_path(Base, NumDigits) :- - WordSize = bits_per_uint, - ( - WordSize = 32, - safe_num_digits_for_base_i32_u32_i64_u64(Base, _, SafeDigits, _, _) - ; - WordSize = 64, - safe_num_digits_for_base_i32_u32_i64_u64(Base, _, _, _, SafeDigits) - ), - NumDigits =< SafeDigits. - -%---------------------% - - % safe_num_digits_for_base_i32_u32_i64_u64(Base, - % SafeDigitsI32, SafeDigitsU32, SafeDigitsI64, SafeDigitsU64): - % - % For a given base Base, SafeDigitsI32 is the largest number of digits - % such that every string of SafeDigits base Base digits denotes a value - % that fits in a 32-bit signed int. That is, SafeDigits is the - % largest number such that: - % - % Base ^ SafeDigits - 1 =< max_int, where max_int = 2 ^ (32 - 1) - 1. - % - % We use the same value of SafeDigits for negative ints. That is safe, - % since abs(min_int) = max_int + 1 (i.e., a bound that holds for max_int - % also holds for min_int). - % - % For a given base Base, SafeDigitsU32 is the largest number of digits - % such that every string of SafeDigits base Base digits denotes a value - % that fits in a 32-bit unsigned int. That is, SafeDigits is the - % largest number such that: - % - % Base ^ SafeDigits - 1 =< max_uint, where max_uint = 2 ^ 32 - 1. - % - % SafeDigitsI64 and SafeDigitsU64 are their 64-bit equivalents. - % -:- pred safe_num_digits_for_base_i32_u32_i64_u64(int::in, - int::out, int::out, int::out, int::out) is semidet. -:- pragma inline(pred(safe_num_digits_for_base_i32_u32_i64_u64/5)). - -safe_num_digits_for_base_i32_u32_i64_u64( 2, 31, 32, 63, 64). -safe_num_digits_for_base_i32_u32_i64_u64( 3, 19, 20, 39, 40). -safe_num_digits_for_base_i32_u32_i64_u64( 4, 15, 16, 31, 32). -safe_num_digits_for_base_i32_u32_i64_u64( 5, 13, 13, 27, 27). -safe_num_digits_for_base_i32_u32_i64_u64( 6, 11, 12, 24, 24). -safe_num_digits_for_base_i32_u32_i64_u64( 7, 11, 11, 22, 22). -safe_num_digits_for_base_i32_u32_i64_u64( 8, 10, 10, 21, 21). -safe_num_digits_for_base_i32_u32_i64_u64( 9, 9, 10, 19, 20). -safe_num_digits_for_base_i32_u32_i64_u64(10, 9, 9, 18, 19). -safe_num_digits_for_base_i32_u32_i64_u64(11, 8, 9, 18, 18). -safe_num_digits_for_base_i32_u32_i64_u64(12, 8, 8, 17, 17). -safe_num_digits_for_base_i32_u32_i64_u64(13, 8, 8, 17, 17). -safe_num_digits_for_base_i32_u32_i64_u64(14, 8, 8, 16, 16). -safe_num_digits_for_base_i32_u32_i64_u64(15, 7, 8, 16, 16). -safe_num_digits_for_base_i32_u32_i64_u64(16, 7, 8, 15, 16). -safe_num_digits_for_base_i32_u32_i64_u64(17, 7, 7, 15, 15). -safe_num_digits_for_base_i32_u32_i64_u64(18, 7, 7, 15, 15). -safe_num_digits_for_base_i32_u32_i64_u64(19, 7, 7, 14, 15). -safe_num_digits_for_base_i32_u32_i64_u64(20, 7, 7, 14, 14). -safe_num_digits_for_base_i32_u32_i64_u64(21, 7, 7, 14, 14). -safe_num_digits_for_base_i32_u32_i64_u64(22, 6, 7, 14, 14). -safe_num_digits_for_base_i32_u32_i64_u64(23, 6, 7, 13, 14). -safe_num_digits_for_base_i32_u32_i64_u64(24, 6, 6, 13, 13). -safe_num_digits_for_base_i32_u32_i64_u64(25, 6, 6, 13, 13). -safe_num_digits_for_base_i32_u32_i64_u64(26, 6, 6, 13, 13). -safe_num_digits_for_base_i32_u32_i64_u64(27, 6, 6, 13, 13). -safe_num_digits_for_base_i32_u32_i64_u64(28, 6, 6, 13, 13). -safe_num_digits_for_base_i32_u32_i64_u64(29, 6, 6, 12, 13). -safe_num_digits_for_base_i32_u32_i64_u64(30, 6, 6, 12, 13). -safe_num_digits_for_base_i32_u32_i64_u64(31, 6, 6, 12, 12). -safe_num_digits_for_base_i32_u32_i64_u64(32, 6, 6, 12, 12). -safe_num_digits_for_base_i32_u32_i64_u64(33, 6, 6, 12, 12). -safe_num_digits_for_base_i32_u32_i64_u64(34, 6, 6, 12, 12). -safe_num_digits_for_base_i32_u32_i64_u64(35, 6, 6, 12, 12). -safe_num_digits_for_base_i32_u32_i64_u64(36, 5, 6, 12, 12). - -%---------------------% - - % do_base_string_to_uint_loop(UBase, Base, CutOff, CutLimit, String, - % CurOffset, EndOffset, !UInt): - % - % This predicate is similar to do_base_string_to_positive_int_loop above. - % -:- pred do_base_string_to_uint_loop(uint::in, int::in, uint::in, uint::in, - string::in, int::in, int::in, uint::in, uint::out) is semidet. - -do_base_string_to_uint_loop(UBase, Base, CutOff, CutLimit, String, - CurOffset, EndOffset, !UInt) :- - ( if CurOffset < EndOffset then - unsafe_index_next(String, CurOffset, NextOffset, Char), - char.unsafe_base_digit_to_int(Base, Char, M), - MU = uint.cast_from_int(M), - % Fail if (UBase * !.UInt) + MU would exceed max_uint. - ( !.UInt < CutOff - ; !.UInt = CutOff, MU =< CutLimit - ), - !:UInt = (UBase * !.UInt) + MU, - do_base_string_to_uint_loop(UBase, Base, CutOff, CutLimit, String, - NextOffset, EndOffset, !UInt) - else - true - ). - - % do_unsafe_base_string_to_uint_loop(UBase, Base, String, CurOffset, - % EndOffset, !UInt): - % - % A version of do_base_string_to_uint_loop that omits the the overflow - % check. This is faster, but can only be used safely when the number of - % digits in the string is below that which potentially overflow. - % -:- pred do_unsafe_base_string_to_uint_loop(uint::in, int::in, string::in, - int::in, int::in, uint::in, uint::out) is semidet. - -do_unsafe_base_string_to_uint_loop(UBase, Base, String, CurOffset, EndOffset, - !UInt) :- - ( if CurOffset < EndOffset then - unsafe_index_next(String, CurOffset, NextOffset, Char), - char.unsafe_base_digit_to_int(Base, Char, M), - MU = uint.cast_from_int(M), - !:UInt = (UBase * !.UInt) + MU, - do_unsafe_base_string_to_uint_loop(UBase, Base, String, - NextOffset, EndOffset, !UInt) - else - true - ). - -%---------------------% +%---------------------------------------------------------------------------% :- pragma foreign_export("C", to_float(in, out), "ML_string_to_float"). @@ -6259,25 +5876,6 @@ int_to_base_string(N, Base, Str) :- ), from_rev_char_list(RevChars, Str). -:- pred int_to_base_string_loop(int::in, int::in, - list(char)::in, list(char)::out) is det. - -int_to_base_string_loop(NegN, Base, !RevChars) :- - % int_to_base_string_loop/3 is almost identical to - % int_to_base_string_group_loop/6 below so any changes here might - % also need to be applied to int_to_base_string_group_loop/3. - ( if NegN > -Base then - N = -NegN, - DigitChar = char.det_base_int_to_digit(Base, N), - !:RevChars = [DigitChar | !.RevChars] - else - NegN1 = NegN // Base, - N10 = (NegN1 * Base) - NegN, - DigitChar = char.det_base_int_to_digit(Base, N10), - int_to_base_string_loop(NegN1, Base, !RevChars), - !:RevChars = [DigitChar | !.RevChars] - ). - %---------------------% int_to_string_thousands(N) = @@ -6302,41 +5900,6 @@ int_to_base_string_group(N, Base, GroupLength, Sep) = Str :- int_to_base_string_group_loop(N1, Base, 0, GroupLength, Sep, Str) ). - % int_to_base_string_group_loop(NegN, Base, Curr, GroupLength, Sep, Str): - % - % GroupLength is how many digits there should be between separators. - % Curr is how many digits have been processed since the last separator - % was inserted. - % int_to_base_string_group_loop/6 is almost identical to - % int_to_base_string_loop/3 above, so any changes here might also - % need to be applied to int_to_base_string_loop/3. - % -:- pred int_to_base_string_group_loop(int::in, int::in, int::in, int::in, - string::in, string::uo) is det. - -int_to_base_string_group_loop(NegN, Base, Curr, GroupLength, Sep, Str) :- - ( if - Curr = GroupLength, - GroupLength > 0 - then - int_to_base_string_group_loop(NegN, Base, 0, GroupLength, Sep, Str1), - string.append(Str1, Sep, Str) - else - ( if NegN > -Base then - N = -NegN, - DigitChar = char.det_base_int_to_digit(Base, N), - string.char_to_string(DigitChar, Str) - else - NegN1 = NegN // Base, - N10 = (NegN1 * Base) - NegN, - DigitChar = char.det_base_int_to_digit(Base, N10), - string.char_to_string(DigitChar, DigitString), - int_to_base_string_group_loop(NegN1, Base, Curr + 1, - GroupLength, Sep, Str1), - string.append(Str1, DigitString, Str) - ) - ). - %---------------------% :- pragma foreign_proc("C", @@ -6365,14 +5928,12 @@ int_to_base_string_group_loop(NegN, Base, Curr, GroupLength, Sep, Str) :- fill_string_with_unsigned_decimal(S, U, num_digits, MR_ALLOC_ID); } "). - :- pragma foreign_proc("C#", int_to_string(I::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], " S = I.ToString(); "). - :- pragma foreign_proc("Java", int_to_string(I::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6400,14 +5961,12 @@ from_int(N) = int_to_string(N). #endif fill_string_with_unsigned_decimal(Str, U, num_digits, MR_ALLOC_ID); "). - :- pragma foreign_proc("C#", uint_to_string(U::in) = (Str::uo), [will_not_call_mercury, promise_pure, thread_safe], " Str = U.ToString(); "). - :- pragma foreign_proc("Java", uint_to_string(U::in) = (Str::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6435,14 +5994,12 @@ uint_to_hex_string(UInt) = #endif fill_string_with_unsigned_hex_lc(Str, U, num_digits, MR_ALLOC_ID); "). - :- pragma foreign_proc("C#", uint_to_lc_hex_string(U::in) = (Str::uo), [will_not_call_mercury, promise_pure, thread_safe], " Str = U.ToString(""x""); "). - :- pragma foreign_proc("Java", uint_to_lc_hex_string(U::in) = (Str::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6465,14 +6022,12 @@ uint_to_hex_string(UInt) = #endif fill_string_with_unsigned_hex_uc(Str, U, num_digits, MR_ALLOC_ID); "). - :- pragma foreign_proc("C#", uint_to_uc_hex_string(U::in) = (Str::uo), [will_not_call_mercury, promise_pure, thread_safe], " Str = U.ToString(""X""); "). - :- pragma foreign_proc("Java", uint_to_uc_hex_string(U::in) = (Str::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6495,14 +6050,12 @@ uint_to_hex_string(UInt) = #endif fill_string_with_unsigned_octal(Str, U, num_digits, MR_ALLOC_ID); "). - :- pragma foreign_proc("C#", uint_to_octal_string(U::in) = (Str::uo), [will_not_call_mercury, promise_pure, thread_safe], " Str = System.Convert.ToString(U, 8); "). - :- pragma foreign_proc("Java", uint_to_octal_string(U::in) = (Str::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6530,14 +6083,12 @@ uint_to_hex_string(UInt) = fill_string_with_unsigned_decimal(S, U8, num_digits, MR_ALLOC_ID); } "). - :- pragma foreign_proc("C#", int8_to_string(I8::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], " S = I8.ToString(); "). - :- pragma foreign_proc("Java", int8_to_string(I8::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6556,14 +6107,12 @@ uint_to_hex_string(UInt) = get_num_decimal_digits_in_uint8(U8, num_digits); fill_string_with_unsigned_decimal(S, U8, num_digits, MR_ALLOC_ID); "). - :- pragma foreign_proc("C#", uint8_to_string(U8::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], " S = U8.ToString(); "). - :- pragma foreign_proc("Java", uint8_to_string(U8::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6591,14 +6140,12 @@ uint_to_hex_string(UInt) = fill_string_with_unsigned_decimal(S, U16, num_digits, MR_ALLOC_ID); } "). - :- pragma foreign_proc("C#", int16_to_string(I16::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], " S = I16.ToString(); "). - :- pragma foreign_proc("Java", int16_to_string(I16::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6617,14 +6164,12 @@ uint_to_hex_string(UInt) = get_num_decimal_digits_in_uint16(U16, num_digits); fill_string_with_unsigned_decimal(S, U16, num_digits, MR_ALLOC_ID); "). - :- pragma foreign_proc("C#", uint16_to_string(U16::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], " S = U16.ToString(); "). - :- pragma foreign_proc("Java", uint16_to_string(U16::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6652,14 +6197,12 @@ uint_to_hex_string(UInt) = fill_string_with_unsigned_decimal(S, U32, num_digits, MR_ALLOC_ID); } "). - :- pragma foreign_proc("C#", int32_to_string(I32::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], " S = I32.ToString(); "). - :- pragma foreign_proc("Java", int32_to_string(I32::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6678,14 +6221,12 @@ uint_to_hex_string(UInt) = get_num_decimal_digits_in_uint32(U32, num_digits); fill_string_with_unsigned_decimal(S, U32, num_digits, MR_ALLOC_ID); "). - :- pragma foreign_proc("C#", uint32_to_string(U32::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], " S = U32.ToString(); "). - :- pragma foreign_proc("Java", uint32_to_string(U32::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6713,14 +6254,12 @@ uint_to_hex_string(UInt) = fill_string_with_unsigned_decimal(S, U64, num_digits, MR_ALLOC_ID); } "). - :- pragma foreign_proc("C#", int64_to_string(I64::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], " S = I64.ToString(); "). - :- pragma foreign_proc("Java", int64_to_string(I64::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6739,14 +6278,12 @@ uint_to_hex_string(UInt) = get_num_decimal_digits_in_uint64(U64, num_digits); fill_string_with_unsigned_decimal(S, U64, num_digits, MR_ALLOC_ID); "). - :- pragma foreign_proc("C#", uint64_to_string(U64::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], " S = U64.ToString(); "). - :- pragma foreign_proc("Java", uint64_to_string(U64::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6770,14 +6307,12 @@ uint64_to_hex_string(UInt) = MR_allocate_aligned_string_msg(S, strlen(buffer), MR_ALLOC_ID); strcpy(S, buffer); "). - :- pragma foreign_proc("C#", uint64_to_lc_hex_string(U64::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], " S = U64.ToString(""x""); "). - :- pragma foreign_proc("Java", uint64_to_lc_hex_string(U64::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6796,14 +6331,12 @@ uint64_to_hex_string(UInt) = MR_allocate_aligned_string_msg(S, strlen(buffer), MR_ALLOC_ID); strcpy(S, buffer); "). - :- pragma foreign_proc("C#", uint64_to_uc_hex_string(U64::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], " S = U64.ToString(""X""); "). - :- pragma foreign_proc("Java", uint64_to_uc_hex_string(U64::in) = (S::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6823,7 +6356,6 @@ uint64_to_hex_string(UInt) = MR_allocate_aligned_string_msg(Str, strlen(buffer), MR_ALLOC_ID); strcpy(Str, buffer); "). - :- pragma foreign_proc("C#", uint64_to_octal_string(U64::in) = (Str::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6833,7 +6365,6 @@ uint64_to_hex_string(UInt) = // unsigned representation for non-decimal bases. Str = System.Convert.ToString((long) U64, 8); "). - :- pragma foreign_proc("Java", uint64_to_octal_string(U64::in) = (Str::uo), [will_not_call_mercury, promise_pure, thread_safe], @@ -6882,7 +6413,6 @@ float_to_string(Float) = S2 :- } } "). - :- pragma foreign_proc("Java", float_to_string(Flt::in, Str::uo), [will_not_call_mercury, promise_pure, thread_safe, will_not_modify_trail, diff --git a/library/string.to_int.m b/library/string.to_int.m new file mode 100644 index 000000000..82de451c1 --- /dev/null +++ b/library/string.to_int.m @@ -0,0 +1,423 @@ +%---------------------------------------------------------------------------% +% vim: ts=4 sw=4 et ft=mercury +%---------------------------------------------------------------------------% +% Copyright (C) 2025-2026 The Mercury team. +% This file is distributed under the terms specified in COPYING.LIB. +%---------------------------------------------------------------------------% +% +% File: string.to_int.m. +% Main author: juliensf. +% +% This module includes the non-interface parts of operations +% that convert from strings to integers. +% +%---------------------------------------------------------------------------% + +:- module string.to_int. +:- interface. + +%---------------------------------------------------------------------------% + + % do_base_string_to_int(Base, String, Int): + % +:- pred do_base_string_to_int(int::in, string::in, int::out) is semidet. + + % do_base_string_to_uint(Base, String, UInt): + % +:- pred do_base_string_to_uint(int::in, string::in, uint::out) is semidet. + +%---------------------------------------------------------------------------% + +:- implementation. + +:- import_module char. +:- import_module int. +:- import_module uint. + +%---------------------------------------------------------------------------% + +:- type int_sign + ---> positive + ; negative. + +%---------------------------------------------------------------------------% + +do_base_string_to_int(Base, String, Int) :- + string.index(String, 0, Char), + End = string.count_code_units(String), + ( if + ( Char = ('-'), Sign0 = negative + ; Char = ('+'), Sign0 = positive + ) + then + Sign = Sign0, + % Start at the first digit, which *should* be just after the sign. + End > 1, + Start = 1 + else + Sign = positive, + Start = 0 + ), + + % Do not include the sign bit, if any, in our initial digit count. + NumStringDigits = End - Start, + + % The divisions below are all safe since our callers set Base + % to be in 2..36. + ( + Sign = positive, + ( if can_use_to_int_fast_path(Base, NumStringDigits) then + do_unsafe_base_string_to_positive_int_loop(Base, String, + Start, End, 0, Int) + else + CutOff = max_int `unchecked_quotient` Base, + CutLimit = max_int `unchecked_rem` Base, + do_base_string_to_positive_int_loop(Base, CutOff, CutLimit, String, + Start, End, 0, Int) + ) + ; + Sign = negative, + ( if can_use_to_int_fast_path(Base, NumStringDigits) then + do_unsafe_base_string_to_negative_int_loop(Base, String, + Start, End, 0, Int) + else + CutOff = min_int `unchecked_quotient` Base, + CutLimit = -(min_int `unchecked_rem` Base), + do_base_string_to_negative_int_loop(Base, CutOff, CutLimit, String, + Start, End, 0, Int) + ) + ). + +%---------------------% + + % do_base_string_to_positive_int_loop(Base, CutOff, CutLimit, String, + % CurOffset, EndOffset, !Int): + % + % Convert the base Base digits of String between CurOffset and EndOffset + % into an int, reading a digit M from the string in each iteration and + % accumulating the result in !Int. Fail if the value being accumulated + % would exceed max_int. + % + % We must detect the overflow *before* it happens. Computing + % (Base * !.Int) + M and then testing the result does not work, + % because the multiplication may overflow by more than the range of + % an int. In that case, the wrapped-around result is again greater + % than !.Int, and so is indistinguishable from a result that did not + % overflow. + % + % Requiring that !.Int =< (max_int - M) // Base be true at each iteration + % of the loop does detect overflow, but at the cost of having a division in + % each iteration. + % + % Instead, we can hoist the division out of the loop body by observing that + % the above check depends on the digit M only through a comparison that + % can be split into cases. Specifically, we can write max_int as: + % + % max_int = (Base * CutOff) + CutLimit + % + % CutOff and CutLimit are invariant and our caller will compute them as: + % + % CutOff = max_int // Base + % CutLimit = max_int rem Base (implying 0 =< CutLimit < Base) + % + % Given these, (Base * !.Int) + M does not exceed max_int if and only + % if either: + % + % - !.Int < CutOff, in which case, since M < Base, + % + % (Base * !.Int) + M =< (Base * (CutOff - 1)) + (Base - 1) + % = (Base * CutOff) - 1 + % =< max_int + % + % whatever the digit M is; or + % + % - !.Int = CutOff and M =< CutLimit, in which case + % + % (Base * !.Int) + M =< (Base * CutOff) + CutLimit = max_int. + % + % If !.Int > CutOff, then + % + % (Base * !.Int) >= (Base * CutOff) + Base, + % + % which exceeds max_int whatever the digit is. + % +:- pred do_base_string_to_positive_int_loop(int::in, int::in, int::in, + string::in, int::in, int::in, int::in, int::out) is semidet. + +do_base_string_to_positive_int_loop(Base, CutOff, CutLimit, String, + CurOffset, EndOffset, !Int) :- + ( if CurOffset < EndOffset then + unsafe_index_next(String, CurOffset, NextOffset, Char), + char.unsafe_base_digit_to_int(Base, Char, M), + % Fail if (Base * !.Int) + M would exceed max_int. + ( !.Int < CutOff + ; !.Int = CutOff, M =< CutLimit + ), + !:Int = (Base * !.Int) + M, + do_base_string_to_positive_int_loop(Base, CutOff, CutLimit, String, + NextOffset, EndOffset, !Int) + else + true + ). + + % do_base_string_to_negative_int_loop(Base, CutOff, CutLimit, String, + % CurOffset, EndOffset, !Int): + % + % This predicate is similar to do_base_string_to_positive_int_loop above, + % but accumulates a negative value, and fail if it would be less than + % min_int. Here our caller gives us + % + % CutOff = min_int // Base + % CutLimit = -(min_int rem Base) + % + % so that min_int = (Base * CutOff) - CutLimit, where CutLimit is + % again in 0 .. (Base - 1). The test mirrors the positive case: the + % step is safe if !.Int > CutOff, whatever the digit M is, or if + % !.Int = CutOff and M =< CutLimit. + % + % Note that we must be the truncating quotient and remainder. + % For a negative dividend, truncation rounds towards zero, which makes + % CutOff the ceiling of the exact quotient min_int / Base; that is the + % tight bound on !.Int. Flooring division yields a CutOff one lower + % whenever Base does not divide min_int exactly, and the test + % !.Int > CutOff would then accept an accumulator value whose next step + % overflows. With 32-bit ints and Base = 10, for example, it would + % convert "-2147483649" to 2147483647 instead of failing. + % + % Note also that we cannot avoid the issue by accumulating a + % positive value and negating it at the end, since the magnitude of + % min_int is not representable as a positive int. + % +:- pred do_base_string_to_negative_int_loop(int::in, int::in, int::in, + string::in, int::in, int::in, int::in, int::out) is semidet. + +do_base_string_to_negative_int_loop(Base, CutOff, CutLimit, String, + CurOffset, EndOffset, !Int) :- + ( if CurOffset < EndOffset then + unsafe_index_next(String, CurOffset, NextOffset, Char), + char.unsafe_base_digit_to_int(Base, Char, M), + % Fail if (Base * !.Int) - M would be less than min_int. + ( !.Int > CutOff + ; !.Int = CutOff, M =< CutLimit + ), + !:Int = (Base * !.Int) - M, + do_base_string_to_negative_int_loop(Base, CutOff, CutLimit, String, + NextOffset, EndOffset, !Int) + else + true + ). + +%---------------------% + + % A version of do_base_string_to_positive_int_loop that omits the the + % overflow check. This is faster, but can only be used safely when the + % number of digits in the string is below that which potentially overflow. + % +:- pred do_unsafe_base_string_to_positive_int_loop(int::in, string::in, + int::in, int::in, int::in, int::out) is semidet. + +do_unsafe_base_string_to_positive_int_loop(Base, String, CurOffset, + EndOffset, !Int) :- + ( if CurOffset < EndOffset then + unsafe_index_next(String, CurOffset, NextOffset, Char), + char.unsafe_base_digit_to_int(Base, Char, M), + !:Int = (Base * !.Int) + M, + do_unsafe_base_string_to_positive_int_loop(Base, String, NextOffset, + EndOffset, !Int) + else + true + ). + + % As above, but for the negative case. + % +:- pred do_unsafe_base_string_to_negative_int_loop(int::in, string::in, + int::in, int::in, int::in, int::out) is semidet. + +do_unsafe_base_string_to_negative_int_loop(Base, String, CurOffset, + EndOffset, !Int) :- + ( if CurOffset < EndOffset then + unsafe_index_next(String, CurOffset, NextOffset, Char), + char.unsafe_base_digit_to_int(Base, Char, M), + !:Int = (Base * !.Int) - M, + do_unsafe_base_string_to_negative_int_loop(Base, String, + NextOffset, EndOffset, !Int) + else + true + ). + +%---------------------------------------------------------------------------% + +do_base_string_to_uint(Base, String, UInt) :- + End = string.count_code_units(String), + End > 0, % Fail if we have the empty string. + UBase = uint.cast_from_int(Base), + % The divisions below are safe since our callers set Base + % to be in 2..36. + ( if can_use_to_uint_fast_path(Base, End) then + do_unsafe_base_string_to_uint_loop(UBase, Base, String, 0, End, + 0u, UInt) + else + CutOff = max_uint `unchecked_quotient` UBase, + CutLimit = max_uint `unchecked_rem` UBase, + do_base_string_to_uint_loop(UBase, Base, CutOff, CutLimit, String, + 0, End, 0u, UInt) + ). + +%---------------------% + + % do_base_string_to_uint_loop(UBase, Base, CutOff, CutLimit, String, + % CurOffset, EndOffset, !UInt): + % + % This predicate is similar to do_base_string_to_positive_int_loop above. + % +:- pred do_base_string_to_uint_loop(uint::in, int::in, uint::in, uint::in, + string::in, int::in, int::in, uint::in, uint::out) is semidet. + +do_base_string_to_uint_loop(UBase, Base, CutOff, CutLimit, String, + CurOffset, EndOffset, !UInt) :- + ( if CurOffset < EndOffset then + unsafe_index_next(String, CurOffset, NextOffset, Char), + char.unsafe_base_digit_to_int(Base, Char, M), + MU = uint.cast_from_int(M), + % Fail if (UBase * !.UInt) + MU would exceed max_uint. + ( !.UInt < CutOff + ; !.UInt = CutOff, MU =< CutLimit + ), + !:UInt = (UBase * !.UInt) + MU, + do_base_string_to_uint_loop(UBase, Base, CutOff, CutLimit, String, + NextOffset, EndOffset, !UInt) + else + true + ). + + % do_unsafe_base_string_to_uint_loop(UBase, Base, String, CurOffset, + % EndOffset, !UInt): + % + % A version of do_base_string_to_uint_loop that omits the the overflow + % check. This is faster, but can only be used safely when the number of + % digits in the string is below that which potentially overflow. + % +:- pred do_unsafe_base_string_to_uint_loop(uint::in, int::in, string::in, + int::in, int::in, uint::in, uint::out) is semidet. + +do_unsafe_base_string_to_uint_loop(UBase, Base, String, CurOffset, EndOffset, + !UInt) :- + ( if CurOffset < EndOffset then + unsafe_index_next(String, CurOffset, NextOffset, Char), + char.unsafe_base_digit_to_int(Base, Char, M), + MU = uint.cast_from_int(M), + !:UInt = (UBase * !.UInt) + MU, + do_unsafe_base_string_to_uint_loop(UBase, Base, String, + NextOffset, EndOffset, !UInt) + else + true + ). + +%---------------------------------------------------------------------------% + + % can_use_to_int_fast_path(Base, NumDigits): + % + % Can we safely use the fast path string-to-int conversion for a string of + % base Base digits of length NumDigits? + % +:- pred can_use_to_int_fast_path(int::in, int::in) is semidet. + +can_use_to_int_fast_path(Base, NumDigits) :- + WordSize = bits_per_int, + ( + WordSize = 32, + safe_num_digits_for_base_i32_u32_i64_u64(Base, SafeDigits, _, _, _) + ; + WordSize = 64, + safe_num_digits_for_base_i32_u32_i64_u64(Base, _, _, SafeDigits, _) + ), + NumDigits =< SafeDigits. + +%---------------------% + + % can_use_to_uint_fast_path(Base, NumDigits): + % + % Can we safely use the fast path string-to-uint conversion for a string of + % base Base digits of length NumDigits? + % +:- pred can_use_to_uint_fast_path(int::in, int::in) is semidet. + +can_use_to_uint_fast_path(Base, NumDigits) :- + WordSize = bits_per_uint, + ( + WordSize = 32, + safe_num_digits_for_base_i32_u32_i64_u64(Base, _, SafeDigits, _, _) + ; + WordSize = 64, + safe_num_digits_for_base_i32_u32_i64_u64(Base, _, _, _, SafeDigits) + ), + NumDigits =< SafeDigits. + +%---------------------% + + % safe_num_digits_for_base_i32_u32_i64_u64(Base, + % SafeDigitsI32, SafeDigitsU32, SafeDigitsI64, SafeDigitsU64): + % + % For a given base Base, SafeDigitsI32 is the largest number of digits + % such that every string of SafeDigits base Base digits denotes a value + % that fits in a 32-bit signed int. That is, SafeDigits is the + % largest number such that: + % + % Base ^ SafeDigits - 1 =< max_int, where max_int = 2 ^ (32 - 1) - 1. + % + % We use the same value of SafeDigits for negative ints. That is safe, + % since abs(min_int) = max_int + 1 (i.e., a bound that holds for max_int + % also holds for min_int). + % + % For a given base Base, SafeDigitsU32 is the largest number of digits + % such that every string of SafeDigits base Base digits denotes a value + % that fits in a 32-bit unsigned int. That is, SafeDigits is the + % largest number such that: + % + % Base ^ SafeDigits - 1 =< max_uint, where max_uint = 2 ^ 32 - 1. + % + % SafeDigitsI64 and SafeDigitsU64 are their 64-bit equivalents. + % +:- pred safe_num_digits_for_base_i32_u32_i64_u64(int::in, + int::out, int::out, int::out, int::out) is semidet. +:- pragma inline(pred(safe_num_digits_for_base_i32_u32_i64_u64/5)). + +safe_num_digits_for_base_i32_u32_i64_u64( 2, 31, 32, 63, 64). +safe_num_digits_for_base_i32_u32_i64_u64( 3, 19, 20, 39, 40). +safe_num_digits_for_base_i32_u32_i64_u64( 4, 15, 16, 31, 32). +safe_num_digits_for_base_i32_u32_i64_u64( 5, 13, 13, 27, 27). +safe_num_digits_for_base_i32_u32_i64_u64( 6, 11, 12, 24, 24). +safe_num_digits_for_base_i32_u32_i64_u64( 7, 11, 11, 22, 22). +safe_num_digits_for_base_i32_u32_i64_u64( 8, 10, 10, 21, 21). +safe_num_digits_for_base_i32_u32_i64_u64( 9, 9, 10, 19, 20). +safe_num_digits_for_base_i32_u32_i64_u64(10, 9, 9, 18, 19). +safe_num_digits_for_base_i32_u32_i64_u64(11, 8, 9, 18, 18). +safe_num_digits_for_base_i32_u32_i64_u64(12, 8, 8, 17, 17). +safe_num_digits_for_base_i32_u32_i64_u64(13, 8, 8, 17, 17). +safe_num_digits_for_base_i32_u32_i64_u64(14, 8, 8, 16, 16). +safe_num_digits_for_base_i32_u32_i64_u64(15, 7, 8, 16, 16). +safe_num_digits_for_base_i32_u32_i64_u64(16, 7, 8, 15, 16). +safe_num_digits_for_base_i32_u32_i64_u64(17, 7, 7, 15, 15). +safe_num_digits_for_base_i32_u32_i64_u64(18, 7, 7, 15, 15). +safe_num_digits_for_base_i32_u32_i64_u64(19, 7, 7, 14, 15). +safe_num_digits_for_base_i32_u32_i64_u64(20, 7, 7, 14, 14). +safe_num_digits_for_base_i32_u32_i64_u64(21, 7, 7, 14, 14). +safe_num_digits_for_base_i32_u32_i64_u64(22, 6, 7, 14, 14). +safe_num_digits_for_base_i32_u32_i64_u64(23, 6, 7, 13, 14). +safe_num_digits_for_base_i32_u32_i64_u64(24, 6, 6, 13, 13). +safe_num_digits_for_base_i32_u32_i64_u64(25, 6, 6, 13, 13). +safe_num_digits_for_base_i32_u32_i64_u64(26, 6, 6, 13, 13). +safe_num_digits_for_base_i32_u32_i64_u64(27, 6, 6, 13, 13). +safe_num_digits_for_base_i32_u32_i64_u64(28, 6, 6, 13, 13). +safe_num_digits_for_base_i32_u32_i64_u64(29, 6, 6, 12, 13). +safe_num_digits_for_base_i32_u32_i64_u64(30, 6, 6, 12, 13). +safe_num_digits_for_base_i32_u32_i64_u64(31, 6, 6, 12, 12). +safe_num_digits_for_base_i32_u32_i64_u64(32, 6, 6, 12, 12). +safe_num_digits_for_base_i32_u32_i64_u64(33, 6, 6, 12, 12). +safe_num_digits_for_base_i32_u32_i64_u64(34, 6, 6, 12, 12). +safe_num_digits_for_base_i32_u32_i64_u64(35, 6, 6, 12, 12). +safe_num_digits_for_base_i32_u32_i64_u64(36, 5, 6, 12, 12). + +%---------------------------------------------------------------------------% +:- end_module string.to_int. +%---------------------------------------------------------------------------%