File 4201-Change-io_lib_format-fwrite_g-1-to-use-Ryu.patch of Package erlang

From c7614bca940da8f05884f08af883c2ef131e0250 Mon Sep 17 00:00:00 2001
From: Thomas Depierre <depierre.thomas@gmail.com>
Date: Mon, 21 Dec 2020 16:11:20 +0100
Subject: [PATCH] Change io_lib_format:fwrite_g/1 to use Ryu

io_lib_format_fwrite_g/1 now use the Ryu algorithm, see
(https://dl.acm.org/doi/pdf/10.1145/3192366.3192369). We get an
approximately 3x speedup over the whole doubles, with a more stable
performance profile. The Dragon4 implementation used before had a
performance profile linear with the size of the double to print.

We also see a 10% reduction in memory use with Ryu over the old
algorithm.

It is worth noting that this implementation is slightly slower (10% to
30%) for "small" doubles in some cases. This commit does not try to
optimise this case yet.

The Ryu algorithm depends on a lookup table that is generated upfront.
This commit add a script to generate it (mostly in case a bug is found
in it or we decide to change the size of the lookup table) and the
output of said script in io_lib_format_ryu_table.erl. This is done
because the table do not change from a build to another and should not
be regenerated outside of a bug or a fundamental change in the
algorithm.

The Ryu algorithm has a know worst case scenario for doubles that can be
represented as "small" integer. This commit introduce a optimised
handling for this case.
---
 lib/stdlib/scripts/generate_ryu_table.escript | 108 +++
 lib/stdlib/src/Makefile                       |   1 +
 lib/stdlib/src/io_lib_format.erl              | 382 ++++++----
 lib/stdlib/src/io_lib_format_ryu_table.erl    | 686 ++++++++++++++++++
 lib/stdlib/src/stdlib.app.src                 |   1 +
 lib/stdlib/test/io_SUITE.erl                  | 192 ++++-
 6 files changed, 1206 insertions(+), 164 deletions(-)
 create mode 100755 lib/stdlib/scripts/generate_ryu_table.escript
 create mode 100644 lib/stdlib/src/io_lib_format_ryu_table.erl

diff --git a/lib/stdlib/scripts/generate_ryu_table.escript b/lib/stdlib/scripts/generate_ryu_table.escript
new file mode 100755
index 0000000000..7191b64cd3
--- /dev/null
+++ b/lib/stdlib/scripts/generate_ryu_table.escript
@@ -0,0 +1,108 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+%%! +A0
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2017-2019. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%%     http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-mode(compile).
+
+-define(MOD, "io_lib_format_ryu_table").
+
+-define(TABLE_SIZE, 326).
+-define(INV_TABLE_SIZE, 342).
+
+-define(POW5_BITCOUNT, 125).
+-define(POW5_INV_BITCOUNT, 125).
+
+main(_) ->
+    Values = [ values(X) || X <- lists:seq(0, ?TABLE_SIZE - 1)],
+    InvValues = [ inv_values(X) || X <- lists:seq(0, ?INV_TABLE_SIZE - 1)],
+
+    %% Make module
+    {ok, Out} = file:open("../src/" ++ ?MOD ++ ".erl", [write]),
+    gen_file(Out, Values, InvValues),
+    ok = file:close(Out),
+    ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+inv_values(X) ->
+  Pow = pow5(X),
+  Pow5len = log2floor(Pow),
+  J = Pow5len + ?POW5_INV_BITCOUNT - 1,
+  Inv = ((1 bsl J) div Pow) + 1,
+  {X, Inv}.
+
+values(X) ->
+  Pow = pow5(X),
+  Pow5len = log2floor(Pow),
+  Pow5 = Pow bsr (Pow5len - ?POW5_BITCOUNT),
+  {X, Pow5}.
+
+pow5(0) ->
+  1;
+pow5(1) ->
+  5;
+pow5(X) ->
+  5 * pow5(X - 1).
+
+log2floor(Int) when is_integer(Int), Int > 0 ->
+    log2floor(Int, 0).
+
+log2floor(0, N) ->
+    N;
+log2floor(Int, N) ->
+    log2floor(Int bsr 1, 1 + N).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+gen_file(Fd, Values, InvValues) ->
+    gen_header(Fd),
+    gen_pow5_static(Fd),
+    gen_table(Fd, Values),
+    gen_inv_table(Fd, InvValues),
+    ok.
+
+gen_header(Fd) ->
+    io:put_chars(Fd, "%%\n%% this file is generated do not modify\n"),
+    io:put_chars(Fd, "%% see ../script/generate_ryu_table.escript\n\n"),
+    io:put_chars(Fd, "-module(" ++ ?MOD ++").\n"),
+    io:put_chars(Fd, "-export([pow5_bitcount/0, pow5_inv_bitcount/0, value/1, inv_value/1]).\n\n"),
+    ok.
+
+gen_pow5_static(Fd) ->
+    io:put_chars(Fd, "-spec pow5_bitcount() -> integer().\n"),
+    io:format(Fd, "pow5_bitcount() -> ~p.~n~n", [?POW5_BITCOUNT]),
+    io:put_chars(Fd, "-spec pow5_inv_bitcount() -> integer().\n"),
+    io:format(Fd, "pow5_inv_bitcount() -> ~p.~n~n", [?POW5_INV_BITCOUNT]),
+    ok.
+
+gen_table(Fd, Values) ->
+    io:put_chars(Fd, "-spec value(integer()) -> integer().\n"),
+    [io:format(Fd, "value(~p) -> ~p;~n", [Key, Val]) || {Key,Val} <- Values],
+    io:put_chars(Fd, "value(_) -> error(function_clause).\n\n"),
+    ok.
+
+gen_inv_table(Fd, Values) ->
+    io:put_chars(Fd, "-spec inv_value(integer()) -> integer().\n"),
+    [io:format(Fd, "inv_value(~p) -> ~p;~n", [Key, Val]) || {Key,Val} <- Values],
+    io:put_chars(Fd, "inv_value(_) -> error(function_clause).\n"),
+    ok.
+
diff --git a/lib/stdlib/src/Makefile b/lib/stdlib/src/Makefile
index 8a45b8e249..509648cb2b 100644
--- a/lib/stdlib/src/Makefile
+++ b/lib/stdlib/src/Makefile
@@ -91,6 +91,7 @@ MODULES= \
 	io \
 	io_lib \
 	io_lib_format \
+	io_lib_format_ryu_table \
 	io_lib_fread \
 	io_lib_pretty \
 	lists \
diff --git a/lib/stdlib/src/io_lib_format.erl b/lib/stdlib/src/io_lib_format.erl
index bf6869a492..9fab476750 100644
--- a/lib/stdlib/src/io_lib_format.erl
+++ b/lib/stdlib/src/io_lib_format.erl
@@ -550,183 +550,257 @@ float_data([_|Cs], Ds) ->
 %%  float(1 bsl 53) is chosen as cutoff point.
 %%
 %%  The algorithm that is used to find the decimal number that is
-%%  represented by the returned String is described in "Printing
-%%  Floating-Point Numbers Quickly and Accurately" in Proceedings of
-%%  the SIGPLAN '96 Conference on Programming Language Design and
-%%  Implementation.
+%%  represented by the returned String is described in "Ryu: Fast
+%%  Float-to-String Conversion" in Proceedings of 39th ACM SIGPLAN
+%%  Conference on Programming Language Design and Implementation.
+%%  https://dl.acm.org/doi/pdf/10.1145/3192366.3192369
 
 -spec fwrite_g(float()) -> string().
-
-fwrite_g(Fl) ->
-    signbit(Fl) ++ abs_fwrite_g(abs(Fl)).
-
-abs_fwrite_g(0.0) ->
-    "0.0";
-abs_fwrite_g(Float) when is_float(Float) ->
-    {Frac, Exp} = mantissa_exponent(Float),
-    {Place, Digits} = fwrite_g_1(Float, Exp, Frac),
-    insert_decimal(Place, [$0 + D || D <- Digits], Float).
-
--define(BIG_POW, (1 bsl 52)).
--define(MIN_EXP, (-1074)).
-
-mantissa_exponent(F) ->
-    case <<F:64/float>> of
-        <<_S:1, 0:11, M:52>> -> % denormalized
-            E = log2floor(M),
-            {M bsl (53 - E), E - 52 - 1075};
-        <<_S:1, BE:11, M:52>> when BE < 2047 ->
-            {M + ?BIG_POW, BE - 1075}
+fwrite_g(Float) ->
+    case sign_mantissa_exponent(Float) of
+        {0, 0, 0} -> "0.0";
+        {1, 0, 0} -> "-0.0";
+        {S, M, E} when E < 2047 ->
+            {Place, Digits} = 
+                case is_small_int(M, E) of
+                    {int, M1, E1} ->
+                        compute_shortest_int(M1, E1);
+                    not_int ->
+                        fwrite_g_1(M, E)
+                end,
+            DigitList = insert_decimal(Place, Digits, Float),
+            insert_minus(S, DigitList)
     end.
 
-fwrite_g_1(Float, Exp, Frac) ->
-    Round = (Frac band 1) =:= 0,
-    if 
-        Exp >= 0  ->
-            BExp = 1 bsl Exp,
-            if
-                Frac =:= ?BIG_POW ->
-                    scale(Frac * BExp * 4, 4, BExp * 2, BExp,
-                          Round, Round, Float);
-                true ->
-                    scale(Frac * BExp * 2, 2, BExp, BExp,
-                          Round, Round, Float)
-            end;
-        Exp < ?MIN_EXP ->
-            BExp = 1 bsl (?MIN_EXP - Exp),
-            scale(Frac * 2, 1 bsl (1 - Exp), BExp, BExp,
-                  Round, Round, Float);
-        Exp > ?MIN_EXP, Frac =:= ?BIG_POW ->
-            scale(Frac * 4, 1 bsl (2 - Exp), 2, 1,
-                  Round, Round, Float);
-        true ->
-            scale(Frac * 2, 1 bsl (1 - Exp), 1, 1,
-                  Round, Round, Float)
-    end.
+-define(BIG_POW, (1 bsl 52)).
+-define(DECODE_CORRECTION, 1075).
 
-scale(R, S, MPlus, MMinus, LowOk, HighOk, Float) ->
-    Est = int_ceil(math:log10(abs(Float)) - 1.0e-10),
-    %% Note that the scheme implementation uses a 326 element look-up
-    %% table for int_pow(10, N) where we do not.
-    if
-        Est >= 0 ->
-            fixup(R, S * int_pow(10, Est), MPlus, MMinus, Est,
-                  LowOk, HighOk);
-        true ->
-            Scale = int_pow(10, -Est),
-            fixup(R * Scale, S, MPlus * Scale, MMinus * Scale, Est,
-                  LowOk, HighOk)
-    end.
+sign_mantissa_exponent(F) ->
+    <<S:1, BE:11, M:52>> = <<F:64/float>>,
+    {S, M , BE}.
 
-fixup(R, S, MPlus, MMinus, K, LowOk, HighOk) ->
-    TooLow = if 
-                 HighOk ->  R + MPlus >= S;
-                 true -> R + MPlus > S
-             end,
-    case TooLow of
+is_small_int(M, E) ->
+    M2 = ?BIG_POW bor M,
+    E2 = E - ?DECODE_CORRECTION,
+    case E2 > 0 orelse E2 < -52 of
         true ->
-            {K + 1, generate(R, S, MPlus, MMinus, LowOk, HighOk)};
-        false ->
-            {K, generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)}
+            %% f = m2 * 2^e2 >= 2^53 is an integer.
+            %% Ignore this case for now.
+            %% or f < 1
+            not_int;
+        _ -> 
+            %% Since 2^52 <= m2 < 2^53 and 0 <= -e2 <= 52: 1 <= f = m2 / 2^-e2 < 2^53.
+            %% Test if the lower -e2 bits of the significand are 0, i.e. whether the fraction is 0.
+            Mask = (1 bsl -E2) - 1,
+            Fraction = M2 band Mask,
+            case Fraction of
+                0 ->
+                %% f is an integer in the range [1, 2^53).
+                %% Note: mantissa might contain trailing (decimal) 0's.
+                    {int, M2 bsr -E2, 0};
+                _ ->
+                    not_int
+            end
     end.
 
-generate(R0, S, MPlus, MMinus, LowOk, HighOk) ->
-    D = R0 div S,
-    R = R0 rem S,
-    TC1 = if
-              LowOk -> R =< MMinus;
-              true -> R < MMinus
-          end,
-    TC2 = if
-              HighOk -> R + MPlus >= S;
-              true -> R + MPlus > S
-          end,
-    case {TC1, TC2} of
-        {false, false} -> 
-            [D | generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)];
-        {false, true} ->
-            [D + 1];
-        {true, false} -> 
-            [D];
-        {true, true} when R * 2 < S ->
-            [D];
-        {true, true} ->
-            [D + 1]
-    end.
+%%  For small integers in the range [1, 2^53), v.mantissa might contain trailing (decimal) zeros.
+compute_shortest_int(M, E) when M rem 10 =:= 0 ->
+    Q = M div 10,
+    compute_shortest_int(Q, E + 1);
+compute_shortest_int(M, E) ->
+    {E, integer_to_list(M)}.
+
+fwrite_g_1(M, E) ->
+    {Mf, Ef} = decode(M, E),
+    Shift = mmshift(M, E),
+    Mv = 4 * Mf,
+    {Q, Vm, Vr, Vp, E10} = convert_to_decimal(Ef, Mv, Shift),
+    Accept = M rem 2 == 0,
+    {VmIsTrailingZero, VrIsTrailingZero, Vp1} = bounds(Mv, Q, Vp, Accept, Ef, Shift),
+    {D1, E1} = compute_shortest(Vm, Vr, Vp1, VmIsTrailingZero, VrIsTrailingZero, Accept),
+    {E1 + E10, integer_to_list(D1)}.
+
+decode(Mantissa, 0) ->
+    {Mantissa, 1 - ?DECODE_CORRECTION - 2};
+decode(Mantissa, Exponent) ->
+    {Mantissa + ?BIG_POW, Exponent - ?DECODE_CORRECTION - 2}.
+
+mmshift(0, E) when E > 1 ->
+    0;
+mmshift(_M, _E) ->
+    1.
+
+convert_to_decimal(E2, Mv, Shift) when E2 >= 0 ->
+    Q = max(0, ((E2 * 78913) bsr 18) - 1),
+    Mul = io_lib_format_ryu_table:inv_value(Q),
+    K = io_lib_format_ryu_table:pow5_inv_bitcount() + pow5bits(Q) - 1,
+    I = -E2 + Q + K,
+    {Vm, Vr, Vp} = mulShiftAll(Mv, Shift, I, Mul),
+    {Q, Vm, Vr, Vp, Q};
+
+convert_to_decimal(E2, Mv, Shift) when E2 < 0 ->
+    Q = max(0, ((-E2 * 732923) bsr 20) - 1),
+    I = -E2 - Q,
+    K = pow5bits(I) - io_lib_format_ryu_table:pow5_bitcount(),
+    From_file = io_lib_format_ryu_table:value(I),
+    J = Q - K,
+    {Vm, Vr, Vp} = mulShiftAll(Mv, Shift, J, From_file),
+    E10 = E2 + Q,
+    {Q, Vm, Vr, Vp, E10}.
+
+pow5bits(E) ->
+    ((E * 1217359) bsr 19) + 1.
+
+mulShiftAll(Mv, Shift, J, Mul) ->
+    A = mulShift64(Mv - 1 - Shift, Mul, J),
+    B = mulShift64(Mv, Mul, J),
+    C = mulShift64(Mv + 2,Mul, J),
+    {A, B, C}.
+
+mulShift64(M, Mul, J) ->
+    (M * Mul) bsr J.
+
+bounds(Mv, Q, Vp, _Accept, E2, _Shift) when E2 >= 0, Q =< 21, Mv rem 5 =:= 0 ->
+    {false, multipleOfPowerOf5(Mv, Q) , Vp};
+bounds(Mv, Q, Vp, true, E2, Shift) when E2 >= 0, Q =< 21 ->
+    {multipleOfPowerOf5(Mv - 1 - Shift, Q), false , Vp};
+bounds(Mv, Q, Vp, _Accept, E2, _Shift) when E2 >= 0, Q =< 21 ->
+    {false, false , Vp - vpmodifier(multipleOfPowerOf5(Mv + 2, Q))};
+bounds(_Mv, Q, Vp, true, E2, Shift) when E2 < 0, Q =< 1 ->
+    {Shift =:= 1, true, Vp};
+bounds(_Mv, Q, Vp, false, E2, _Shift) when E2 < 0, Q =< 1 ->
+    {false, true, Vp - 1};
+bounds(Mv, Q, Vp, _Accept, E2, _Shift) when E2 < 0, Q < 63 ->
+    {false, (Mv band ((1 bsl Q) -1 )) =:= 0, Vp};
+bounds(_Mv, _Q, Vp, _Accept, _E2, _Shift) ->
+    {false, false, Vp}.
+
+multipleOfPowerOf5(Value, Q) ->
+    pow5factor(Value) >= Q.
+
+pow5factor(Val) ->
+    pow5factor(Val div 5, 0).
+
+pow5factor(Val, Count) when (Val rem 5) /= 0->
+    Count;
+pow5factor(Val, Count) ->
+    pow5factor(Val div 5, Count + 1).
+
+vpmodifier(true) ->
+    1;
+vpmodifier(false) ->
+    0.
+
+compute_shortest(Vm, Vr, Vp, false, false, _Accept) ->
+    {Vm1, Vr1, Removed, RoundUp} =
+        general_case(Vm, Vr, Vp, 0, false),
+    Output = Vr1 + handle_normal_output_mod(Vr1, Vm1, RoundUp),
+    {Output, Removed};
+compute_shortest(Vm, Vr, Vp, VmIsTrailingZero, VrIsTrailingZero, Accept) ->
+    {Vm1, Vr1, Removed, LastRemovedDigit} = 
+        handle_trailing_zeros(Vm, Vr, Vp, VmIsTrailingZero, VrIsTrailingZero, 0, 0),
+    Output = Vr1 + handle_zero_output_mod(Vr1, Vm1, Accept, VmIsTrailingZero, LastRemovedDigit),
+    {Output, Removed}.
+
+general_case(Vm, Vr, Vp, Removed, RoundUp) when (Vp div 100) =< (Vm div 100)->
+    general_case_10(Vm, Vr, Vp, Removed, RoundUp);
+general_case(Vm, Vr, Vp, Removed, _RU) ->
+    VmD100 = Vm div 100,
+    VrD100 = Vr div 100,
+    VpD100 = Vp div 100,
+    RoundUp = ((Vr rem 100) >= 50),
+    general_case_10(VmD100, VrD100, VpD100, 2 + Removed, RoundUp).
+
+general_case_10(Vm, Vr, Vp, Removed, RoundUp) 
+    when (Vp div 10) =< (Vm div 10)->
+    {Vm, Vr, Removed, RoundUp};
+general_case_10(Vm, Vr, Vp, Removed, _RU) ->
+    VmD10 = Vm div 10,
+    VrD10 = Vr div 10,
+    VpD10 = Vp div 10,
+    RoundUp = ((Vr rem 10) >= 5),
+    general_case_10(VmD10, VrD10, VpD10, 1 + Removed, RoundUp).
+
+handle_normal_output_mod(Vr, Vm, RoundUp) when (Vm =:= Vr) or RoundUp ->
+    1;
+handle_normal_output_mod(_Vr, _Vm, _RoundUp) ->
+    0.
+
+handle_trailing_zeros(Vm, Vr, Vp, VmTZ, VrTZ, Removed, LastRemovedDigit)
+    when (Vp div 10) =< (Vm div 10)->
+    vmIsTrailingZero(Vm, Vr, Vp, VmTZ, VrTZ, Removed, LastRemovedDigit);
+handle_trailing_zeros(Vm, Vr, Vp, VmIsTrailingZero, VrIsTrailingZero, Removed, LastRemovedDigit) ->
+    VmTZ = VmIsTrailingZero and ((Vm rem 10) =:= 0),
+    VrTZ = VrIsTrailingZero and (LastRemovedDigit =:= 0),
+    handle_trailing_zeros(Vm div 10, Vr div 10, Vp div 10, VmTZ, VrTZ, 1 + Removed, Vr rem 10).
+
+vmIsTrailingZero(Vm, Vr, _Vp, false = _VmTZ, VrTZ, Removed, LastRemovedDigit) ->
+    handle_50_dotdot_0(Vm, Vr, VrTZ, Removed, LastRemovedDigit);
+vmIsTrailingZero(Vm, Vr, _Vp, _VmTZ, VrTZ, Removed, LastRemovedDigit) when (Vm rem 10) /= 0 ->
+    handle_50_dotdot_0(Vm, Vr, VrTZ, Removed, LastRemovedDigit);
+vmIsTrailingZero(Vm, Vr, Vp, VmTZ, VrTZ, Removed, LastRemovedDigit) ->
+    vmIsTrailingZero(Vm div 10, Vr div 10, Vp div 10, VmTZ, LastRemovedDigit == 0 andalso VrTZ, 1 + Removed, Vr rem 10).
+
+handle_50_dotdot_0(Vm, Vr, true, Removed, 5) when (Vr rem 2) =:= 0 ->
+    {Vm, Vr, Removed, 4};
+handle_50_dotdot_0(Vm, Vr, _VrTZ, Removed, LastRemovedDigit) ->
+    {Vm, Vr, Removed, LastRemovedDigit}.
+
+handle_zero_output_mod(_Vr, _Vm, _Accept, _VmTZ, LastRemovedDigit) when LastRemovedDigit >= 5 ->
+    1;
+handle_zero_output_mod(Vr, Vm, Accept, VmTZ, _LastRemovedDigit) when Vr =:= Vm, ((not Accept) or (not VmTZ)) ->
+    1;
+handle_zero_output_mod(_Vr, _Vm, _Accept, _VmTZ, _LastRemovedDigit) ->
+    0.
 
-insert_decimal(0, S, _) ->
-    "0." ++ S;
 insert_decimal(Place, S, Float) ->
     L = length(S),
+    Exp = Place + L - 1,
+    ExpL = integer_to_list(Exp),
+    ExpCost = length(ExpL) + 2,
     if
-        Place < 0;
-        Place >= L ->
-            ExpL = integer_to_list(Place - 1),
-            ExpDot = if L =:= 1 -> 2; true -> 1 end,
-            ExpCost = length(ExpL) + 1 + ExpDot,
-            if 
-                Place < 0 ->
-                    if 
-                        2 - Place =< ExpCost ->
-                            "0." ++ lists:duplicate(-Place, $0) ++ S;
-                        true ->
-                            insert_exp(ExpL, S)
-                    end;
+        Place < 0 ->
+            if
+                Exp >= 0 ->
+                    {S0, S1} = lists:split(L + Place, S),
+                    S0 ++ "." ++ S1;
+                2 - Place - L =< ExpCost ->
+                    %% All integers in the range [-2^53, 2^53] can
+                    %% be stored without loss of precision in an
+                    %% IEEE 754 64-bit double but 2^53+1 cannot be
+                    %% stored in an IEEE 754 64-bit double without
+                    %% loss of precision (float((1 bsl 53)+1) =:=
+                    %% float(1 bsl 53)). It thus makes sense to
+                    %% show floats that are >= 2^53 or <= -2^53 in
+                    %% scientific notation to indicate that the
+                    %% number is so large that there could be loss
+                    %% in precion when adding or subtracting 1.
+                    %%
+                    %% https://stackoverflow.com/questions/1848700/biggest-integer-that-can-be-stored-in-a-double?answertab=votes#tab-top
+                    "0." ++ lists:duplicate(-Place - L, $0) ++ S;
                 true ->
-                    if
-                        %% All integers in the range [-2^53, 2^53] can
-                        %% be stored without loss of precision in an
-                        %% IEEE 754 64-bit double but 2^53+1 cannot be
-                        %% stored in an IEEE 754 64-bit double without
-                        %% loss of precision (float((1 bsl 53)+1) =:=
-                        %% float(1 bsl 53)). It thus makes sense to
-                        %% show floats that are >= 2^53 or <= -2^53 in
-                        %% scientific notation to indicate that the
-                        %% number is so large that there could be loss
-                        %% in precion when adding or subtracting 1.
-                        %%
-                        %% https://stackoverflow.com/questions/1848700/biggest-integer-that-can-be-stored-in-a-double?answertab=votes#tab-top
-                        Place - L + 2 =< ExpCost andalso abs(Float) < float(1 bsl 53) ->
-                            S ++ lists:duplicate(Place - L, $0) ++ ".0";
-                        true ->
-                            insert_exp(ExpL, S)
-                    end
+                    insert_exp(ExpL, S)
             end;
         true ->
-            {S0, S1} = lists:split(Place, S),
-            S0 ++ "." ++ S1
+            Dot = if L =:= 1 -> 1; true -> 0 end,
+            if
+                ExpCost + Dot >= Place + 2 andalso abs(Float) < float(1 bsl 53) ->
+                    S ++ lists:duplicate(Place, $0) ++ ".0";
+                true ->
+                    insert_exp(ExpL, S)
+            end
     end.
 
+
 insert_exp(ExpL, [C]) ->
     [C] ++ ".0e" ++ ExpL;
 insert_exp(ExpL, [C | S]) ->
     [C] ++ "." ++ S ++ "e" ++ ExpL.
 
-int_ceil(X) when is_float(X) ->
-    T = trunc(X),
-    case (X - T) of
-        Neg when Neg < 0 -> T;
-        Pos when Pos > 0 -> T + 1;
-        _ -> T
-    end.
-
-int_pow(X, 0) when is_integer(X) ->
-    1;
-int_pow(X, N) when is_integer(X), is_integer(N), N > 0 ->
-    int_pow(X, N, 1).
-
-int_pow(X, N, R) when N < 2 ->
-    R * X;
-int_pow(X, N, R) ->
-    int_pow(X * X, N bsr 1, case N band 1 of 1 -> R * X; 0 -> R end).
-
-log2floor(Int) when is_integer(Int), Int > 0 ->
-    log2floor(Int, 0).
-
-log2floor(0, N) ->
-    N;
-log2floor(Int, N) ->
-    log2floor(Int bsr 1, 1 + N).
+insert_minus(0, Digits) ->
+    Digits;
+insert_minus(1, Digits) ->
+    [$-] ++ Digits.
 
 %% fwrite_g(Float, Field, Adjust, Precision, PadChar)
 %%  Use the f form if Float is >= 0.1 and < 1.0e4, 
diff --git a/lib/stdlib/src/io_lib_format_ryu_table.erl b/lib/stdlib/src/io_lib_format_ryu_table.erl
new file mode 100644
index 0000000000..b20268a939
--- /dev/null
+++ b/lib/stdlib/src/io_lib_format_ryu_table.erl
@@ -0,0 +1,686 @@
+%%
+%% this file is generated do not modify
+%% see ../script/generate_ryu_table.escript
+
+-module(io_lib_format_ryu_table).
+-export([pow5_bitcount/0, pow5_inv_bitcount/0, value/1, inv_value/1]).
+
+-spec pow5_bitcount() -> integer().
+pow5_bitcount() -> 125.
+
+-spec pow5_inv_bitcount() -> integer().
+pow5_inv_bitcount() -> 125.
+
+-spec value(integer()) -> integer().
+value(0) -> 21267647932558653966460912964485513216;
+value(1) -> 26584559915698317458076141205606891520;
+value(2) -> 33230699894622896822595176507008614400;
+value(3) -> 41538374868278621028243970633760768000;
+value(4) -> 25961484292674138142652481646100480000;
+value(5) -> 32451855365842672678315602057625600000;
+value(6) -> 40564819207303340847894502572032000000;
+value(7) -> 25353012004564588029934064107520000000;
+value(8) -> 31691265005705735037417580134400000000;
+value(9) -> 39614081257132168796771975168000000000;
+value(10) -> 24758800785707605497982484480000000000;
+value(11) -> 30948500982134506872478105600000000000;
+value(12) -> 38685626227668133590597632000000000000;
+value(13) -> 24178516392292583494123520000000000000;
+value(14) -> 30223145490365729367654400000000000000;
+value(15) -> 37778931862957161709568000000000000000;
+value(16) -> 23611832414348226068480000000000000000;
+value(17) -> 29514790517935282585600000000000000000;
+value(18) -> 36893488147419103232000000000000000000;
+value(19) -> 23058430092136939520000000000000000000;
+value(20) -> 28823037615171174400000000000000000000;
+value(21) -> 36028797018963968000000000000000000000;
+value(22) -> 22517998136852480000000000000000000000;
+value(23) -> 28147497671065600000000000000000000000;
+value(24) -> 35184372088832000000000000000000000000;
+value(25) -> 21990232555520000000000000000000000000;
+value(26) -> 27487790694400000000000000000000000000;
+value(27) -> 34359738368000000000000000000000000000;
+value(28) -> 21474836480000000000000000000000000000;
+value(29) -> 26843545600000000000000000000000000000;
+value(30) -> 33554432000000000000000000000000000000;
+value(31) -> 41943040000000000000000000000000000000;
+value(32) -> 26214400000000000000000000000000000000;
+value(33) -> 32768000000000000000000000000000000000;
+value(34) -> 40960000000000000000000000000000000000;
+value(35) -> 25600000000000000000000000000000000000;
+value(36) -> 32000000000000000000000000000000000000;
+value(37) -> 40000000000000000000000000000000000000;
+value(38) -> 25000000000000000000000000000000000000;
+value(39) -> 31250000000000000000000000000000000000;
+value(40) -> 39062500000000000000000000000000000000;
+value(41) -> 24414062500000000000000000000000000000;
+value(42) -> 30517578125000000000000000000000000000;
+value(43) -> 38146972656250000000000000000000000000;
+value(44) -> 23841857910156250000000000000000000000;
+value(45) -> 29802322387695312500000000000000000000;
+value(46) -> 37252902984619140625000000000000000000;
+value(47) -> 23283064365386962890625000000000000000;
+value(48) -> 29103830456733703613281250000000000000;
+value(49) -> 36379788070917129516601562500000000000;
+value(50) -> 22737367544323205947875976562500000000;
+value(51) -> 28421709430404007434844970703125000000;
+value(52) -> 35527136788005009293556213378906250000;
+value(53) -> 22204460492503130808472633361816406250;
+value(54) -> 27755575615628913510590791702270507812;
+value(55) -> 34694469519536141888238489627838134765;
+value(56) -> 21684043449710088680149056017398834228;
+value(57) -> 27105054312137610850186320021748542785;
+value(58) -> 33881317890172013562732900027185678482;
+value(59) -> 42351647362715016953416125033982098102;
+value(60) -> 26469779601696885595885078146238811314;
+value(61) -> 33087224502121106994856347682798514142;
+value(62) -> 41359030627651383743570434603498142678;
+value(63) -> 25849394142282114839731521627186339173;
+value(64) -> 32311742677852643549664402033982923967;
+value(65) -> 40389678347315804437080502542478654959;
+value(66) -> 25243548967072377773175314089049159349;
+value(67) -> 31554436208840472216469142611311449186;
+value(68) -> 39443045261050590270586428264139311483;
+value(69) -> 24651903288156618919116517665087069677;
+value(70) -> 30814879110195773648895647081358837096;
+value(71) -> 38518598887744717061119558851698546370;
+value(72) -> 24074124304840448163199724282311591481;
+value(73) -> 30092655381050560203999655352889489352;
+value(74) -> 37615819226313200254999569191111861690;
+value(75) -> 23509887016445750159374730744444913556;
+value(76) -> 29387358770557187699218413430556141945;
+value(77) -> 36734198463196484624023016788195177431;
+value(78) -> 22958874039497802890014385492621985894;
+value(79) -> 28698592549372253612517981865777482368;
+value(80) -> 35873240686715317015647477332221852960;
+value(81) -> 22420775429197073134779673332638658100;
+value(82) -> 28025969286496341418474591665798322625;
+value(83) -> 35032461608120426773093239582247903282;
+value(84) -> 21895288505075266733183274738904939551;
+value(85) -> 27369110631344083416479093423631174439;
+value(86) -> 34211388289180104270598866779538968048;
+value(87) -> 21382117680737565169124291737211855030;
+value(88) -> 26727647100921956461405364671514818788;
+value(89) -> 33409558876152445576756705839393523485;
+value(90) -> 41761948595190556970945882299241904356;
+value(91) -> 26101217871994098106841176437026190222;
+value(92) -> 32626522339992622633551470546282737778;
+value(93) -> 40783152924990778291939338182853422223;
+value(94) -> 25489470578119236432462086364283388889;
+value(95) -> 31861838222649045540577607955354236111;
+value(96) -> 39827297778311306925722009944192795139;
+value(97) -> 24892061111444566828576256215120496962;
+value(98) -> 31115076389305708535720320268900621202;
+value(99) -> 38893845486632135669650400336125776503;
+value(100) -> 24308653429145084793531500210078610314;
+value(101) -> 30385816786431355991914375262598262893;
+value(102) -> 37982270983039194989892969078247828616;
+value(103) -> 23738919364399496868683105673904892885;
+value(104) -> 29673649205499371085853882092381116106;
+value(105) -> 37092061506874213857317352615476395133;
+value(106) -> 23182538441796383660823345384672746958;
+value(107) -> 28978173052245479576029181730840933698;
+value(108) -> 36222716315306849470036477163551167122;
+value(109) -> 22639197697066780918772798227219479451;
+value(110) -> 28298997121333476148465997784024349314;
+value(111) -> 35373746401666845185582497230030436643;
+value(112) -> 22108591501041778240989060768769022902;
+value(113) -> 27635739376302222801236325960961278627;
+value(114) -> 34544674220377778501545407451201598284;
+value(115) -> 21590421387736111563465879657000998927;
+value(116) -> 26988026734670139454332349571251248659;
+value(117) -> 33735033418337674317915436964064060824;
+value(118) -> 42168791772922092897394296205080076030;
+value(119) -> 26355494858076308060871435128175047519;
+value(120) -> 32944368572595385076089293910218809399;
+value(121) -> 41180460715744231345111617387773511748;
+value(122) -> 25737787947340144590694760867358444843;
+value(123) -> 32172234934175180738368451084198056053;
+value(124) -> 40215293667718975922960563855247570067;
+value(125) -> 25134558542324359951850352409529731292;
+value(126) -> 31418198177905449939812940511912164115;
+value(127) -> 39272747722381812424766175639890205143;
+value(128) -> 24545467326488632765478859774931378214;
+value(129) -> 30681834158110790956848574718664222768;
+value(130) -> 38352292697638488696060718398330278460;
+value(131) -> 23970182936024055435037948998956424037;
+value(132) -> 29962728670030069293797436248695530047;
+value(133) -> 37453410837537586617246795310869412559;
+value(134) -> 23408381773460991635779247069293382849;
+value(135) -> 29260477216826239544724058836616728561;
+value(136) -> 36575596521032799430905073545770910702;
+value(137) -> 22859747825645499644315670966106819189;
+value(138) -> 28574684782056874555394588707633523986;
+value(139) -> 35718355977571093194243235884541904982;
+value(140) -> 22323972485981933246402022427838690614;
+value(141) -> 27904965607477416558002528034798363267;
+value(142) -> 34881207009346770697503160043497954084;
+value(143) -> 21800754380841731685939475027186221303;
+value(144) -> 27250942976052164607424343783982776628;
+value(145) -> 34063678720065205759280429729978470785;
+value(146) -> 21289799200040753599550268581236544241;
+value(147) -> 26612249000050941999437835726545680301;
+value(148) -> 33265311250063677499297294658182100376;
+value(149) -> 41581639062579596874121618322727625471;
+value(150) -> 25988524414112248046326011451704765919;
+value(151) -> 32485655517640310057907514314630957399;
+value(152) -> 40607069397050387572384392893288696749;
+value(153) -> 25379418373156492232740245558305435468;
+value(154) -> 31724272966445615290925306947881794335;
+value(155) -> 39655341208057019113656633684852242919;
+value(156) -> 24784588255035636946035396053032651824;
+value(157) -> 30980735318794546182544245066290814780;
+value(158) -> 38725919148493182728180306332863518475;
+value(159) -> 24203699467808239205112691458039699047;
+value(160) -> 30254624334760299006390864322549623809;
+value(161) -> 37818280418450373757988580403187029761;
+value(162) -> 23636425261531483598742862751991893600;
+value(163) -> 29545531576914354498428578439989867001;
+value(164) -> 36931914471142943123035723049987333751;
+value(165) -> 23082446544464339451897326906242083594;
+value(166) -> 28853058180580424314871658632802604493;
+value(167) -> 36066322725725530393589573291003255616;
+value(168) -> 22541451703578456495993483306877034760;
+value(169) -> 28176814629473070619991854133596293450;
+value(170) -> 35221018286841338274989817666995366813;
+value(171) -> 22013136429275836421868636041872104258;
+value(172) -> 27516420536594795527335795052340130322;
+value(173) -> 34395525670743494409169743815425162903;
+value(174) -> 21497203544214684005731089884640726814;
+value(175) -> 26871504430268355007163862355800908518;
+value(176) -> 33589380537835443758954827944751135647;
+value(177) -> 41986725672294304698693534930938919559;
+value(178) -> 26241703545183940436683459331836824724;
+value(179) -> 32802129431479925545854324164796030906;
+value(180) -> 41002661789349906932317905205995038632;
+value(181) -> 25626663618343691832698690753746899145;
+value(182) -> 32033329522929614790873363442183623931;
+value(183) -> 40041661903662018488591704302729529914;
+value(184) -> 25026038689788761555369815189205956196;
+value(185) -> 31282548362235951944212268986507445245;
+value(186) -> 39103185452794939930265336233134306557;
+value(187) -> 24439490907996837456415835145708941598;
+value(188) -> 30549363634996046820519793932136176997;
+value(189) -> 38186704543745058525649742415170221247;
+value(190) -> 23866690339840661578531089009481388279;
+value(191) -> 29833362924800826973163861261851735349;
+value(192) -> 37291703656001033716454826577314669186;
+value(193) -> 23307314785000646072784266610821668241;
+value(194) -> 29134143481250807590980333263527085302;
+value(195) -> 36417679351563509488725416579408856627;
+value(196) -> 22761049594727193430453385362130535392;
+value(197) -> 28451311993408991788066731702663169240;
+value(198) -> 35564139991761239735083414628328961550;
+value(199) -> 22227587494850774834427134142705600969;
+value(200) -> 27784484368563468543033917678382001211;
+value(201) -> 34730605460704335678792397097977501514;
+value(202) -> 21706628412940209799245248186235938446;
+value(203) -> 27133285516175262249056560232794923058;
+value(204) -> 33916606895219077811320700290993653822;
+value(205) -> 42395758619023847264150875363742067278;
+value(206) -> 26497349136889904540094297102338792048;
+value(207) -> 33121686421112380675117871377923490061;
+value(208) -> 41402108026390475843897339222404362576;
+value(209) -> 25876317516494047402435837014002726610;
+value(210) -> 32345396895617559253044796267503408262;
+value(211) -> 40431746119521949066305995334379260328;
+value(212) -> 25269841324701218166441247083987037705;
+value(213) -> 31587301655876522708051558854983797131;
+value(214) -> 39484127069845653385064448568729746414;
+value(215) -> 24677579418653533365665280355456091509;
+value(216) -> 30846974273316916707081600444320114386;
+value(217) -> 38558717841646145883852000555400142982;
+value(218) -> 24099198651028841177407500347125089364;
+value(219) -> 30123998313786051471759375433906361705;
+value(220) -> 37654997892232564339699219292382952131;
+value(221) -> 23534373682645352712312012057739345082;
+value(222) -> 29417967103306690890390015072174181352;
+value(223) -> 36772458879133363612987518840217726691;
+value(224) -> 22982786799458352258117199275136079181;
+value(225) -> 28728483499322940322646499093920098977;
+value(226) -> 35910604374153675403308123867400123721;
+value(227) -> 22444127733846047127067577417125077326;
+value(228) -> 28055159667307558908834471771406346657;
+value(229) -> 35068949584134448636043089714257933322;
+value(230) -> 21918093490084030397526931071411208326;
+value(231) -> 27397616862605037996908663839264010407;
+value(232) -> 34247021078256297496135829799080013009;
+value(233) -> 21404388173910185935084893624425008131;
+value(234) -> 26755485217387732418856117030531260163;
+value(235) -> 33444356521734665523570146288164075204;
+value(236) -> 41805445652168331904462682860205094006;
+value(237) -> 26128403532605207440289176787628183753;
+value(238) -> 32660504415756509300361470984535229692;
+value(239) -> 40825630519695636625451838730669037115;
+value(240) -> 25516019074809772890907399206668148197;
+value(241) -> 31895023843512216113634249008335185246;
+value(242) -> 39868779804390270142042811260418981558;
+value(243) -> 24917987377743918838776757037761863473;
+value(244) -> 31147484222179898548470946297202329342;
+value(245) -> 38934355277724873185588682871502911677;
+value(246) -> 24333972048578045740992926794689319798;
+value(247) -> 30417465060722557176241158493361649748;
+value(248) -> 38021831325903196470301448116702062185;
+value(249) -> 23763644578689497793938405072938788865;
+value(250) -> 29704555723361872242423006341173486082;
+value(251) -> 37130694654202340303028757926466857602;
+value(252) -> 23206684158876462689392973704041786001;
+value(253) -> 29008355198595578361741217130052232502;
+value(254) -> 36260443998244472952176521412565290627;
+value(255) -> 22662777498902795595110325882853306642;
+value(256) -> 28328471873628494493887907353566633302;
+value(257) -> 35410589842035618117359884191958291628;
+value(258) -> 22131618651272261323349927619973932267;
+value(259) -> 27664523314090326654187409524967415334;
+value(260) -> 34580654142612908317734261906209269168;
+value(261) -> 21612908839133067698583913691380793230;
+value(262) -> 27016136048916334623229892114225991537;
+value(263) -> 33770170061145418279037365142782489422;
+value(264) -> 42212712576431772848796706428478111778;
+value(265) -> 26382945360269858030497941517798819861;
+value(266) -> 32978681700337322538122426897248524826;
+value(267) -> 41223352125421653172653033621560656033;
+value(268) -> 25764595078388533232908146013475410020;
+value(269) -> 32205743847985666541135182516844262526;
+value(270) -> 40257179809982083176418978146055328157;
+value(271) -> 25160737381238801985261861341284580098;
+value(272) -> 31450921726548502481577326676605725123;
+value(273) -> 39313652158185628101971658345757156403;
+value(274) -> 24571032598866017563732286466098222752;
+value(275) -> 30713790748582521954665358082622778440;
+value(276) -> 38392238435728152443331697603278473050;
+value(277) -> 23995149022330095277082311002049045656;
+value(278) -> 29993936277912619096352888752561307070;
+value(279) -> 37492420347390773870441110940701633838;
+value(280) -> 23432762717119233669025694337938521149;
+value(281) -> 29290953396399042086282117922423151436;
+value(282) -> 36613691745498802607852647403028939295;
+value(283) -> 22883557340936751629907904626893087059;
+value(284) -> 28604446676170939537384880783616358824;
+value(285) -> 35755558345213674421731100979520448530;
+value(286) -> 22347223965758546513581938112200280331;
+value(287) -> 27934029957198183141977422640250350414;
+value(288) -> 34917537446497728927471778300312938018;
+value(289) -> 21823460904061080579669861437695586261;
+value(290) -> 27279326130076350724587326797119482826;
+value(291) -> 34099157662595438405734158496399353533;
+value(292) -> 21311973539122149003583849060249595958;
+value(293) -> 26639966923902686254479811325311994947;
+value(294) -> 33299958654878357818099764156639993684;
+value(295) -> 41624948318597947272624705195799992106;
+value(296) -> 26015592699123717045390440747374995066;
+value(297) -> 32519490873904646306738050934218743833;
+value(298) -> 40649363592380807883422563667773429791;
+value(299) -> 25405852245238004927139102292358393619;
+value(300) -> 31757315306547506158923877865447992024;
+value(301) -> 39696644133184382698654847331809990030;
+value(302) -> 24810402583240239186659279582381243769;
+value(303) -> 31013003229050298983324099477976554711;
+value(304) -> 38766254036312873729155124347470693389;
+value(305) -> 24228908772695546080721952717169183368;
+value(306) -> 30286135965869432600902440896461479210;
+value(307) -> 37857669957336790751128051120576849012;
+value(308) -> 23661043723335494219455031950360530633;
+value(309) -> 29576304654169367774318789937950663291;
+value(310) -> 36970380817711709717898487422438329114;
+value(311) -> 23106488011069818573686554639023955696;
+value(312) -> 28883110013837273217108193298779944620;
+value(313) -> 36103887517296591521385241623474930775;
+value(314) -> 22564929698310369700865776014671831734;
+value(315) -> 28206162122887962126082220018339789668;
+value(316) -> 35257702653609952657602775022924737085;
+value(317) -> 22036064158506220411001734389327960678;
+value(318) -> 27545080198132775513752167986659950848;
+value(319) -> 34431350247665969392190209983324938560;
+value(320) -> 21519593904791230870118881239578086600;
+value(321) -> 26899492380989038587648601549472608250;
+value(322) -> 33624365476236298234560751936840760312;
+value(323) -> 42030456845295372793200939921050950390;
+value(324) -> 26269035528309607995750587450656843994;
+value(325) -> 32836294410387009994688234313321054992;
+value(_) -> error(function_clause).
+
+-spec inv_value(integer()) -> integer().
+inv_value(0) -> 42535295865117307932921825928971026433;
+inv_value(1) -> 34028236692093846346337460743176821146;
+inv_value(2) -> 27222589353675077077069968594541456917;
+inv_value(3) -> 21778071482940061661655974875633165534;
+inv_value(4) -> 34844914372704098658649559801013064854;
+inv_value(5) -> 27875931498163278926919647840810451883;
+inv_value(6) -> 22300745198530623141535718272648361506;
+inv_value(7) -> 35681192317648997026457149236237378410;
+inv_value(8) -> 28544953854119197621165719388989902728;
+inv_value(9) -> 22835963083295358096932575511191922183;
+inv_value(10) -> 36537540933272572955092120817907075492;
+inv_value(11) -> 29230032746618058364073696654325660394;
+inv_value(12) -> 23384026197294446691258957323460528315;
+inv_value(13) -> 37414441915671114706014331717536845304;
+inv_value(14) -> 29931553532536891764811465374029476243;
+inv_value(15) -> 23945242826029513411849172299223580995;
+inv_value(16) -> 38312388521647221458958675678757729591;
+inv_value(17) -> 30649910817317777167166940543006183673;
+inv_value(18) -> 24519928653854221733733552434404946938;
+inv_value(19) -> 39231885846166754773973683895047915101;
+inv_value(20) -> 31385508676933403819178947116038332081;
+inv_value(21) -> 25108406941546723055343157692830665665;
+inv_value(22) -> 40173451106474756888549052308529065064;
+inv_value(23) -> 32138760885179805510839241846823252051;
+inv_value(24) -> 25711008708143844408671393477458601641;
+inv_value(25) -> 41137613933030151053874229563933762625;
+inv_value(26) -> 32910091146424120843099383651147010100;
+inv_value(27) -> 26328072917139296674479506920917608080;
+inv_value(28) -> 42124916667422874679167211073468172928;
+inv_value(29) -> 33699933333938299743333768858774538343;
+inv_value(30) -> 26959946667150639794667015087019630674;
+inv_value(31) -> 21567957333720511835733612069615704539;
+inv_value(32) -> 34508731733952818937173779311385127263;
+inv_value(33) -> 27606985387162255149739023449108101810;
+inv_value(34) -> 22085588309729804119791218759286481448;
+inv_value(35) -> 35336941295567686591665950014858370317;
+inv_value(36) -> 28269553036454149273332760011886696254;
+inv_value(37) -> 22615642429163319418666208009509357003;
+inv_value(38) -> 36185027886661311069865932815214971205;
+inv_value(39) -> 28948022309329048855892746252171976964;
+inv_value(40) -> 23158417847463239084714197001737581571;
+inv_value(41) -> 37053468555941182535542715202780130514;
+inv_value(42) -> 29642774844752946028434172162224104411;
+inv_value(43) -> 23714219875802356822747337729779283529;
+inv_value(44) -> 37942751801283770916395740367646853646;
+inv_value(45) -> 30354201441027016733116592294117482917;
+inv_value(46) -> 24283361152821613386493273835293986334;
+inv_value(47) -> 38853377844514581418389238136470378133;
+inv_value(48) -> 31082702275611665134711390509176302507;
+inv_value(49) -> 24866161820489332107769112407341042006;
+inv_value(50) -> 39785858912782931372430579851745667209;
+inv_value(51) -> 31828687130226345097944463881396533767;
+inv_value(52) -> 25462949704181076078355571105117227014;
+inv_value(53) -> 40740719526689721725368913768187563222;
+inv_value(54) -> 32592575621351777380295131014550050577;
+inv_value(55) -> 26074060497081421904236104811640040462;
+inv_value(56) -> 41718496795330275046777767698624064739;
+inv_value(57) -> 33374797436264220037422214158899251791;
+inv_value(58) -> 26699837949011376029937771327119401433;
+inv_value(59) -> 21359870359209100823950217061695521147;
+inv_value(60) -> 34175792574734561318320347298712833834;
+inv_value(61) -> 27340634059787649054656277838970267067;
+inv_value(62) -> 21872507247830119243725022271176213654;
+inv_value(63) -> 34996011596528190789960035633881941846;
+inv_value(64) -> 27996809277222552631968028507105553477;
+inv_value(65) -> 22397447421778042105574422805684442782;
+inv_value(66) -> 35835915874844867368919076489095108450;
+inv_value(67) -> 28668732699875893895135261191276086760;
+inv_value(68) -> 22934986159900715116108208953020869408;
+inv_value(69) -> 36695977855841144185773134324833391053;
+inv_value(70) -> 29356782284672915348618507459866712843;
+inv_value(71) -> 23485425827738332278894805967893370274;
+inv_value(72) -> 37576681324381331646231689548629392439;
+inv_value(73) -> 30061345059505065316985351638903513951;
+inv_value(74) -> 24049076047604052253588281311122811161;
+inv_value(75) -> 38478521676166483605741250097796497857;
+inv_value(76) -> 30782817340933186884593000078237198286;
+inv_value(77) -> 24626253872746549507674400062589758629;
+inv_value(78) -> 39402006196394479212279040100143613806;
+inv_value(79) -> 31521604957115583369823232080114891045;
+inv_value(80) -> 25217283965692466695858585664091912836;
+inv_value(81) -> 40347654345107946713373737062547060537;
+inv_value(82) -> 32278123476086357370698989650037648430;
+inv_value(83) -> 25822498780869085896559191720030118744;
+inv_value(84) -> 41315998049390537434494706752048189990;
+inv_value(85) -> 33052798439512429947595765401638551992;
+inv_value(86) -> 26442238751609943958076612321310841594;
+inv_value(87) -> 42307582002575910332922579714097346550;
+inv_value(88) -> 33846065602060728266338063771277877240;
+inv_value(89) -> 27076852481648582613070451017022301792;
+inv_value(90) -> 21661481985318866090456360813617841434;
+inv_value(91) -> 34658371176510185744730177301788546293;
+inv_value(92) -> 27726696941208148595784141841430837035;
+inv_value(93) -> 22181357552966518876627313473144669628;
+inv_value(94) -> 35490172084746430202603701557031471404;
+inv_value(95) -> 28392137667797144162082961245625177124;
+inv_value(96) -> 22713710134237715329666368996500141699;
+inv_value(97) -> 36341936214780344527466190394400226718;
+inv_value(98) -> 29073548971824275621972952315520181375;
+inv_value(99) -> 23258839177459420497578361852416145100;
+inv_value(100) -> 37214142683935072796125378963865832159;
+inv_value(101) -> 29771314147148058236900303171092665728;
+inv_value(102) -> 23817051317718446589520242536874132582;
+inv_value(103) -> 38107282108349514543232388058998612131;
+inv_value(104) -> 30485825686679611634585910447198889705;
+inv_value(105) -> 24388660549343689307668728357759111764;
+inv_value(106) -> 39021856878949902892269965372414578822;
+inv_value(107) -> 31217485503159922313815972297931663058;
+inv_value(108) -> 24973988402527937851052777838345330446;
+inv_value(109) -> 39958381444044700561684444541352528714;
+inv_value(110) -> 31966705155235760449347555633082022971;
+inv_value(111) -> 25573364124188608359478044506465618377;
+inv_value(112) -> 40917382598701773375164871210344989403;
+inv_value(113) -> 32733906078961418700131896968275991523;
+inv_value(114) -> 26187124863169134960105517574620793218;
+inv_value(115) -> 41899399781070615936168828119393269149;
+inv_value(116) -> 33519519824856492748935062495514615319;
+inv_value(117) -> 26815615859885194199148049996411692255;
+inv_value(118) -> 21452492687908155359318439997129353804;
+inv_value(119) -> 34323988300653048574909503995406966087;
+inv_value(120) -> 27459190640522438859927603196325572870;
+inv_value(121) -> 21967352512417951087942082557060458296;
+inv_value(122) -> 35147764019868721740707332091296733273;
+inv_value(123) -> 28118211215894977392565865673037386618;
+inv_value(124) -> 22494568972715981914052692538429909295;
+inv_value(125) -> 35991310356345571062484308061487854871;
+inv_value(126) -> 28793048285076456849987446449190283897;
+inv_value(127) -> 23034438628061165479989957159352227118;
+inv_value(128) -> 36855101804897864767983931454963563388;
+inv_value(129) -> 29484081443918291814387145163970850711;
+inv_value(130) -> 23587265155134633451509716131176680569;
+inv_value(131) -> 37739624248215413522415545809882688910;
+inv_value(132) -> 30191699398572330817932436647906151128;
+inv_value(133) -> 24153359518857864654345949318324920902;
+inv_value(134) -> 38645375230172583446953518909319873443;
+inv_value(135) -> 30916300184138066757562815127455898755;
+inv_value(136) -> 24733040147310453406050252101964719004;
+inv_value(137) -> 39572864235696725449680403363143550406;
+inv_value(138) -> 31658291388557380359744322690514840325;
+inv_value(139) -> 25326633110845904287795458152411872260;
+inv_value(140) -> 40522612977353446860472733043858995616;
+inv_value(141) -> 32418090381882757488378186435087196493;
+inv_value(142) -> 25934472305506205990702549148069757194;
+inv_value(143) -> 41495155688809929585124078636911611511;
+inv_value(144) -> 33196124551047943668099262909529289209;
+inv_value(145) -> 26556899640838354934479410327623431367;
+inv_value(146) -> 42491039425341367895167056524197490187;
+inv_value(147) -> 33992831540273094316133645219357992150;
+inv_value(148) -> 27194265232218475452906916175486393720;
+inv_value(149) -> 21755412185774780362325532940389114976;
+inv_value(150) -> 34808659497239648579720852704622583961;
+inv_value(151) -> 27846927597791718863776682163698067169;
+inv_value(152) -> 22277542078233375091021345730958453735;
+inv_value(153) -> 35644067325173400145634153169533525976;
+inv_value(154) -> 28515253860138720116507322535626820781;
+inv_value(155) -> 22812203088110976093205858028501456625;
+inv_value(156) -> 36499524940977561749129372845602330600;
+inv_value(157) -> 29199619952782049399303498276481864480;
+inv_value(158) -> 23359695962225639519442798621185491584;
+inv_value(159) -> 37375513539561023231108477793896786534;
+inv_value(160) -> 29900410831648818584886782235117429227;
+inv_value(161) -> 23920328665319054867909425788093943382;
+inv_value(162) -> 38272525864510487788655081260950309411;
+inv_value(163) -> 30618020691608390230924065008760247529;
+inv_value(164) -> 24494416553286712184739252007008198023;
+inv_value(165) -> 39191066485258739495582803211213116837;
+inv_value(166) -> 31352853188206991596466242568970493469;
+inv_value(167) -> 25082282550565593277172994055176394776;
+inv_value(168) -> 40131652080904949243476790488282231641;
+inv_value(169) -> 32105321664723959394781432390625785313;
+inv_value(170) -> 25684257331779167515825145912500628250;
+inv_value(171) -> 41094811730846668025320233460001005200;
+inv_value(172) -> 32875849384677334420256186768000804160;
+inv_value(173) -> 26300679507741867536204949414400643328;
+inv_value(174) -> 42081087212386988057927919063041029325;
+inv_value(175) -> 33664869769909590446342335250432823460;
+inv_value(176) -> 26931895815927672357073868200346258768;
+inv_value(177) -> 21545516652742137885659094560277007015;
+inv_value(178) -> 34472826644387420617054551296443211223;
+inv_value(179) -> 27578261315509936493643641037154568979;
+inv_value(180) -> 22062609052407949194914912829723655183;
+inv_value(181) -> 35300174483852718711863860527557848292;
+inv_value(182) -> 28240139587082174969491088422046278634;
+inv_value(183) -> 22592111669665739975592870737637022907;
+inv_value(184) -> 36147378671465183960948593180219236651;
+inv_value(185) -> 28917902937172147168758874544175389321;
+inv_value(186) -> 23134322349737717735007099635340311457;
+inv_value(187) -> 37014915759580348376011359416544498331;
+inv_value(188) -> 29611932607664278700809087533235598665;
+inv_value(189) -> 23689546086131422960647270026588478932;
+inv_value(190) -> 37903273737810276737035632042541566291;
+inv_value(191) -> 30322618990248221389628505634033253033;
+inv_value(192) -> 24258095192198577111702804507226602426;
+inv_value(193) -> 38812952307517723378724487211562563882;
+inv_value(194) -> 31050361846014178702979589769250051106;
+inv_value(195) -> 24840289476811342962383671815400040885;
+inv_value(196) -> 39744463162898148739813874904640065415;
+inv_value(197) -> 31795570530318518991851099923712052332;
+inv_value(198) -> 25436456424254815193480879938969641866;
+inv_value(199) -> 40698330278807704309569407902351426985;
+inv_value(200) -> 32558664223046163447655526321881141588;
+inv_value(201) -> 26046931378436930758124421057504913271;
+inv_value(202) -> 41675090205499089212999073692007861233;
+inv_value(203) -> 33340072164399271370399258953606288986;
+inv_value(204) -> 26672057731519417096319407162885031189;
+inv_value(205) -> 21337646185215533677055525730308024951;
+inv_value(206) -> 34140233896344853883288841168492839922;
+inv_value(207) -> 27312187117075883106631072934794271938;
+inv_value(208) -> 21849749693660706485304858347835417550;
+inv_value(209) -> 34959599509857130376487773356536668080;
+inv_value(210) -> 27967679607885704301190218685229334464;
+inv_value(211) -> 22374143686308563440952174948183467571;
+inv_value(212) -> 35798629898093701505523479917093548114;
+inv_value(213) -> 28638903918474961204418783933674838491;
+inv_value(214) -> 22911123134779968963535027146939870793;
+inv_value(215) -> 36657797015647950341656043435103793269;
+inv_value(216) -> 29326237612518360273324834748083034615;
+inv_value(217) -> 23460990090014688218659867798466427692;
+inv_value(218) -> 37537584144023501149855788477546284307;
+inv_value(219) -> 30030067315218800919884630782037027446;
+inv_value(220) -> 24024053852175040735907704625629621957;
+inv_value(221) -> 38438486163480065177452327401007395130;
+inv_value(222) -> 30750788930784052141961861920805916104;
+inv_value(223) -> 24600631144627241713569489536644732884;
+inv_value(224) -> 39361009831403586741711183258631572614;
+inv_value(225) -> 31488807865122869393368946606905258091;
+inv_value(226) -> 25191046292098295514695157285524206473;
+inv_value(227) -> 40305674067357272823512251656838730356;
+inv_value(228) -> 32244539253885818258809801325470984285;
+inv_value(229) -> 25795631403108654607047841060376787428;
+inv_value(230) -> 41273010244973847371276545696602859885;
+inv_value(231) -> 33018408195979077897021236557282287908;
+inv_value(232) -> 26414726556783262317616989245825830326;
+inv_value(233) -> 42263562490853219708187182793321328522;
+inv_value(234) -> 33810849992682575766549746234657062818;
+inv_value(235) -> 27048679994146060613239796987725650254;
+inv_value(236) -> 21638943995316848490591837590180520204;
+inv_value(237) -> 34622310392506957584946940144288832325;
+inv_value(238) -> 27697848314005566067957552115431065860;
+inv_value(239) -> 22158278651204452854366041692344852688;
+inv_value(240) -> 35453245841927124566985666707751764301;
+inv_value(241) -> 28362596673541699653588533366201411441;
+inv_value(242) -> 22690077338833359722870826692961129153;
+inv_value(243) -> 36304123742133375556593322708737806644;
+inv_value(244) -> 29043298993706700445274658166990245316;
+inv_value(245) -> 23234639194965360356219726533592196253;
+inv_value(246) -> 37175422711944576569951562453747514004;
+inv_value(247) -> 29740338169555661255961249962998011203;
+inv_value(248) -> 23792270535644529004768999970398408963;
+inv_value(249) -> 38067632857031246407630399952637454340;
+inv_value(250) -> 30454106285624997126104319962109963472;
+inv_value(251) -> 24363285028499997700883455969687970778;
+inv_value(252) -> 38981256045599996321413529551500753244;
+inv_value(253) -> 31185004836479997057130823641200602595;
+inv_value(254) -> 24948003869183997645704658912960482076;
+inv_value(255) -> 39916806190694396233127454260736771322;
+inv_value(256) -> 31933444952555516986501963408589417058;
+inv_value(257) -> 25546755962044413589201570726871533646;
+inv_value(258) -> 40874809539271061742722513162994453834;
+inv_value(259) -> 32699847631416849394178010530395563067;
+inv_value(260) -> 26159878105133479515342408424316450454;
+inv_value(261) -> 41855804968213567224547853478906320726;
+inv_value(262) -> 33484643974570853779638282783125056581;
+inv_value(263) -> 26787715179656683023710626226500045265;
+inv_value(264) -> 21430172143725346418968500981200036212;
+inv_value(265) -> 34288275429960554270349601569920057938;
+inv_value(266) -> 27430620343968443416279681255936046351;
+inv_value(267) -> 21944496275174754733023745004748837081;
+inv_value(268) -> 35111194040279607572837992007598139329;
+inv_value(269) -> 28088955232223686058270393606078511463;
+inv_value(270) -> 22471164185778948846616314884862809171;
+inv_value(271) -> 35953862697246318154586103815780494673;
+inv_value(272) -> 28763090157797054523668883052624395738;
+inv_value(273) -> 23010472126237643618935106442099516591;
+inv_value(274) -> 36816755401980229790296170307359226545;
+inv_value(275) -> 29453404321584183832236936245887381236;
+inv_value(276) -> 23562723457267347065789548996709904989;
+inv_value(277) -> 37700357531627755305263278394735847982;
+inv_value(278) -> 30160286025302204244210622715788678386;
+inv_value(279) -> 24128228820241763395368498172630942709;
+inv_value(280) -> 38605166112386821432589597076209508334;
+inv_value(281) -> 30884132889909457146071677660967606667;
+inv_value(282) -> 24707306311927565716857342128774085334;
+inv_value(283) -> 39531690099084105146971747406038536534;
+inv_value(284) -> 31625352079267284117577397924830829227;
+inv_value(285) -> 25300281663413827294061918339864663382;
+inv_value(286) -> 40480450661462123670499069343783461410;
+inv_value(287) -> 32384360529169698936399255475026769128;
+inv_value(288) -> 25907488423335759149119404380021415303;
+inv_value(289) -> 41451981477337214638591047008034264484;
+inv_value(290) -> 33161585181869771710872837606427411587;
+inv_value(291) -> 26529268145495817368698270085141929270;
+inv_value(292) -> 42446829032793307789917232136227086832;
+inv_value(293) -> 33957463226234646231933785708981669466;
+inv_value(294) -> 27165970580987716985547028567185335573;
+inv_value(295) -> 21732776464790173588437622853748268458;
+inv_value(296) -> 34772442343664277741500196565997229533;
+inv_value(297) -> 27817953874931422193200157252797783626;
+inv_value(298) -> 22254363099945137754560125802238226901;
+inv_value(299) -> 35606980959912220407296201283581163042;
+inv_value(300) -> 28485584767929776325836961026864930433;
+inv_value(301) -> 22788467814343821060669568821491944347;
+inv_value(302) -> 36461548502950113697071310114387110955;
+inv_value(303) -> 29169238802360090957657048091509688764;
+inv_value(304) -> 23335391041888072766125638473207751011;
+inv_value(305) -> 37336625667020916425801021557132401617;
+inv_value(306) -> 29869300533616733140640817245705921294;
+inv_value(307) -> 23895440426893386512512653796564737035;
+inv_value(308) -> 38232704683029418420020246074503579256;
+inv_value(309) -> 30586163746423534736016196859602863405;
+inv_value(310) -> 24468930997138827788812957487682290724;
+inv_value(311) -> 39150289595422124462100731980291665158;
+inv_value(312) -> 31320231676337699569680585584233332127;
+inv_value(313) -> 25056185341070159655744468467386665702;
+inv_value(314) -> 40089896545712255449191149547818665122;
+inv_value(315) -> 32071917236569804359352919638254932098;
+inv_value(316) -> 25657533789255843487482335710603945678;
+inv_value(317) -> 41052054062809349579971737136966313085;
+inv_value(318) -> 32841643250247479663977389709573050468;
+inv_value(319) -> 26273314600197983731181911767658440375;
+inv_value(320) -> 42037303360316773969891058828253504599;
+inv_value(321) -> 33629842688253419175912847062602803679;
+inv_value(322) -> 26903874150602735340730277650082242944;
+inv_value(323) -> 21523099320482188272584222120065794355;
+inv_value(324) -> 34436958912771501236134755392105270968;
+inv_value(325) -> 27549567130217200988907804313684216774;
+inv_value(326) -> 22039653704173760791126243450947373419;
+inv_value(327) -> 35263445926678017265801989521515797471;
+inv_value(328) -> 28210756741342413812641591617212637977;
+inv_value(329) -> 22568605393073931050113273293770110382;
+inv_value(330) -> 36109768628918289680181237270032176610;
+inv_value(331) -> 28887814903134631744144989816025741288;
+inv_value(332) -> 23110251922507705395315991852820593031;
+inv_value(333) -> 36976403076012328632505586964512948849;
+inv_value(334) -> 29581122460809862906004469571610359079;
+inv_value(335) -> 23664897968647890324803575657288287263;
+inv_value(336) -> 37863836749836624519685721051661259621;
+inv_value(337) -> 30291069399869299615748576841329007697;
+inv_value(338) -> 24232855519895439692598861473063206158;
+inv_value(339) -> 38772568831832703508158178356901129852;
+inv_value(340) -> 31018055065466162806526542685520903882;
+inv_value(341) -> 24814444052372930245221234148416723105;
+inv_value(_) -> error(function_clause).
diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src
index 9c580bffca..a906349463 100644
--- a/lib/stdlib/src/stdlib.app.src
+++ b/lib/stdlib/src/stdlib.app.src
@@ -71,6 +71,7 @@
 	     io,
 	     io_lib,
 	     io_lib_format,
+	     io_lib_format_ryu_table,
 	     io_lib_fread,
 	     io_lib_pretty,
 	     lists,
diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl
index bb59a5e53f..22b598c3c0 100644
--- a/lib/stdlib/test/io_SUITE.erl
+++ b/lib/stdlib/test/io_SUITE.erl
@@ -985,15 +985,12 @@ otp_7084(Config) when is_list(Config) ->
          {g_denormalized, fun g_denormalized/0},
          {g_normalized, fun g_normalized/0},
          {g_choice, fun g_choice/0},
+         {g_ryu, fun g_ryu/0},
+         {g_anomalous, fun g_anomalous/0},
          {g_misc, fun g_misc/0}],
     F = fun({M,T}) -> io:format("~p~n", [M]), T() end,
-    R = try 
-            lists:foreach(fun(T) -> F(T) end, L),
-            ok
-        catch throw:Reason -> 
-            Reason
-        end,
-    R.
+    lists:foreach(fun(T) -> F(T) end, L),
+    ok.
 
 g_warm_up() ->
     g_t(0.5),
@@ -1041,13 +1038,188 @@ g_normalized() ->
 g_choice() ->
     %% Exponent should be used when and only when the string is shorter.
     %% (g_misc/0 checks this too, and probably more throughly).
-    L = [0.0003, 3.0e-5, 3.3e-5,    3.3e-4,
-         314.0,  314.1,  310.0,     3.1e6,   -100.0,
-         3.34e4, 3.0e3,  3.34333e9, 3.3433323e10, 33433323700.0,
+    L = [0.0003, 3.0e-5, 3.3e-5, 3.3e-4,
+         314.0, 314.1, 310.0, 3.1e6, -100.0,
+         3.34e4, 3.0e3, 3.34333e9, 3.3433323e10, 33433323700.0,
          0.00197963, 1.97963e-4],
     lists:foreach(fun(V) -> g_t(V) end, L),
     ok.
 
+g_anomalous() ->
+    %% These test cases come from https://github.com/microsoft/STL/blob/f1515e04fd00876137e762c08b90d9aa450859e0/tests/std/tests/P0067R5_charconv/double_to_chars_test_cases.hpp
+
+    %% https://www.exploringbinary.com/the-shortest-decimal-string-that-round-trips-may-not-be-the-nearest/
+    %% This is an exhaustive list of anomalous values
+    %% Because math, these values have shortest-round-trip decimal representations containing 16 significant digits,
+    %% but those decimal digits aren't what would be printed by "%.15e". For ordinary values, shortest-round-trip
+    %% behaves as if it can magically pick a precision for "%.*e", finding the smallest precision that round-trips.
+    %% (That is, start with the exact decimal representation, and then round it as much as possible.) These anomalous
+    %% values demonstrate an exception to that mental model. They aren't being "incorrectly rounded"; instead, having
+    %% the shortest output that round-trips is being prioritized. (This differs by 1 in the least significant decimal
+    %% digit printed, so it's a very small difference.)
+
+    L_anom = [6.386688990511104e+293, 5.282945311356653e+269,
+              6.150157786156811e+259, 5.334411546303884e+241,
+              5.386379163185535e+213, 6.483618076376552e+178,
+              6.183260036827614e+172, 5.896816288783659e+166,
+              5.758609657015292e+163, 5.623642243178996e+160,
+              6.243497100631985e+144, 8.263199609878108e+121,
+              6.455624695217272e+119, 6.156563468186638e+113,
+              7.167183174968974e+103, 6.518515124270356e+91,
+              6.070840288205404e+82, 6.129982163463556e+54,
+              5.986310706507379e+51, 5.444517870735016e+39,
+              5.316911983139664e+36, 6.189700196426902e+26,
+              5.960464477539063e-08, 5.684341886080802e-14,
+              6.617444900424222e-24, 6.310887241768095e-30,
+              7.174648137343064e-43, 7.854549544476363e-90,
+              6.653062250012736e-111, 5.075883674631299e-116,
+              6.256509672447191e-148, 4.887898181599368e-150,
+              5.966672584960166e-154, 5.426657103235053e-166,
+              5.351097043477547e-197, 5.225680706521042e-200,
+              6.083493012144512e-210, 5.940911144672375e-213,
+              6.290184345309701e-235, 6.142758149716505e-238,
+              7.678447687145631e-239, 5.858190679279809e-244,
+              5.641232424577593e-278, 8.209073602596753e-289,
+              7.291122019556398e-304, 7.120236347223045e-307],
+    lists:foreach(fun(V) -> g_t(V) end, L_anom),
+
+    %% This is an exhaustive list of almost-but-not-quite-anomalous values.
+    L_quasi_anom = [6.237000967296e+290, 6.090821257125e+287,
+                    8.25460204899477e+267, 5.78358058743443e+222,
+                    7.1362384635298e+44, 6.10987272699921e-151,
+                    5.17526350329881e-172, 6.84940421565126e-195],
+    lists:foreach(fun(V) -> g_t(V) end, L_quasi_anom),
+
+    ok.
+
+g_ryu() ->
+    %% specific white box tests that should trigger specific edge cases
+    %% to the ryu algorithm see: 
+    %% https://github.com/ulfjack/ryu/blob/master/ryu/tests/d2s_test.cc
+
+    %% this list is regression tests from the ryu C ref implementation
+    L_regression = [-2.109808898695963e16, 4.940656e-318, 1.18575755E-316,
+                    2.989102097996e-312, 9.0608011534336e15,
+                    4.708356024711512e18, 9.409340012568248e18,
+                    1.2345678],
+    lists:foreach(fun(V) -> g_t(V) end, L_regression),
+
+    %% These numbers have a mantissa that is a multiple of the largest power of 5 that fits,
+    %% and an exponent that causes the computation for q to result in 22, which is a corner
+    %% case for Ryu.
+    L_pow5 = [16#4830F0CF064DD592, 16#4840F0CF064DD592, 
+              16#4850F0CF064DD592],
+    lists:foreach(fun(V) -> g_t(i_2_d(V)) end, L_pow5),
+
+    %% Test 32-bit chunking 2^32 +- 1/2
+    L_32bits = [4.294967294, 4.294967295, 4.294967296, 4.294967297,
+                4.294967298],
+    lists:foreach(fun(V) -> g_t(V) end, L_32bits),
+
+    %% Test 32-bit chunking 2^32 +- 1/2
+    L_32bits = [4.294967294, 4.294967295, 4.294967296, 4.294967297,
+                4.294967298],
+    lists:foreach(fun(V) -> g_t(V) end, L_32bits),
+
+    L = [1.2e+1, 1.23e+2, 1.234e+3, 1.2345e+4, 1.23456e+5, 1.234567e+6,
+        1.2345678e+7, 1.23456789e+8, 1.23456789e+9, 1.234567895e+9,
+        1.2345678901e+10, 1.23456789012e+11, 1.234567890123e+12,
+        1.2345678901234e+13, 1.23456789012345e+14, 1.234567890123456e+15],
+    lists:foreach(fun(V) -> g_t(V) end, L),
+
+    %% power of 2
+    L_pow2 = [8.0, 64.0, 512.0, 8192.0, 65536.0, 524288.0, 8388608.0,
+             67108864.0, 536870912.0, 8589934592.0, 68719476736.0,
+             549755813888.0, 8796093022208.0, 70368744177664.0,
+             562949953421312.0, 9007199254740992.0],
+    lists:foreach(fun(V) -> g_t(V) end, L_pow2),
+
+    %% 1000 * power of 2
+    L_pow2_1000 = [8.0e+3, 64.0e+3, 512.0e+3, 8192.0e+3, 65536.0e+3,
+                  524288.0e+3, 8388608.0e+3, 67108864.0e+3, 536870912.0e+3,
+                  8589934592.0e+3, 68719476736.0e+3, 549755813888.0e+3,
+                  8796093022208.0e+3],
+    lists:foreach(fun(V) -> g_t(V) end, L_pow2_1000),
+
+    %% 10^15 + 10^i
+    L_pow10_plus = [1.0e+15 + 1.0e+0, 1.0e+15 + 1.0e+1, 1.0e+15 + 1.0e+2,
+                   1.0e+15 + 1.0e+3, 1.0e+15 + 1.0e+4, 1.0e+15 + 1.0e+5,
+                   1.0e+15 + 1.0e+6, 1.0e+15 + 1.0e+7, 1.0e+15 + 1.0e+8,
+                   1.0e+15 + 1.0e+9, 1.0e+15 + 1.0e+10, 1.0e+15 + 1.0e+11,
+                   1.0e+15 + 1.0e+12, 1.0e+15 + 1.0e+13, 1.0e+15 + 1.0e+14],
+    lists:foreach(fun(V) -> g_t(V) end, L_pow10_plus),
+
+    %% min and max
+    g_t(i_2_d(1)),
+    g_t(i_2_d(16#7fefffffffffffff)),
+
+    %% lots of trailing zeroes
+    g_t(2.98023223876953125e-8),
+
+    %% Switch to Subnormal
+    g_t(2.2250738585072014e-308),
+
+    %% special case to check for the shift to the right by 128
+    L_shift = [parts_2_f(0, 4,0), parts_2_f(0, 6, (1 bsl 53) - 1),
+               parts_2_f(0, 41, 0), parts_2_f(0, 40, (1 bsl 53) - 1),
+               parts_2_f(0, 1077, 0), parts_2_f(0, 1076, (1 bsl 53) - 1),
+               parts_2_f(0, 307, 0), parts_2_f(0, 306, (1 bsl 53) - 1),
+               parts_2_f(0, 934, 16#000FA7161A4D6E0C)],
+    lists:foreach(fun(V) -> g_t(V) end, L_shift),
+
+    %% following test cases come from https://github.com/microsoft/STL/blob/f1515e04fd00876137e762c08b90d9aa450859e0/tests/std/tests/P0067R5_charconv/double_to_chars_test_cases.hpp
+    %% These numbers have odd mantissas (unaffected by shifting)
+    %% that are barely within the "max shifted mantissa" limit.
+
+    L_mantissas_within_limit = [
+        1801439850948197.0e1, 360287970189639.0e2, 72057594037927.0e3,
+        14411518807585.0e4, 2882303761517.0e5, 576460752303.0e6,
+        115292150459.0e7, 23058430091.0e8, 4611686017.0e9, 922337203.0e10,
+        184467439.0e11, 36893487.0e12, 7378697.0e13, 1475739.0e14,
+        295147.0e15, 59029.0e16, 11805.0e17, 2361.0e18, 471.0e19, 93.0e20,
+        17.0e21, 3.0e22],
+    lists:foreach(fun(V) -> g_t(V) end, L_mantissas_within_limit),
+
+    %% These numbers have odd mantissas (unaffected by shifting)
+    %% that are barely above the "max shifted mantissa" limit.
+    L_mantissas_above_limit = [
+        1801439850948199.0e1, 360287970189641.0e2, 72057594037929.0e3,
+        14411518807587.0e4, 2882303761519.0e5, 576460752305.0e6,
+        115292150461.0e7, 23058430093.0e8, 4611686019.0e9, 922337205.0e10,
+        184467441.0e11, 36893489.0e12, 7378699.0e13, 1475741.0e14,
+        295149.0e15, 59031.0e16, 11807.0e17, 2363.0e18, 473.0e19, 95.0e20,
+        19.0e21, 5.0e22],
+    lists:foreach(fun(V) -> g_t(V) end, L_mantissas_above_limit),
+
+    L_switch = [1801439850948197.0e1, 360287970189639.0e2, 72057594037927.0e3,
+                14411518807585.0e4, 2882303761517.0e5, 576460752303.0e6,
+                115292150459.0e7, 23058430091.0e8, 4611686017.0e9,
+                922337203.0e10, 184467439.0e11, 36893487.0e12, 7378697.0e13,
+                1475739.0e14, 295147.0e15, 59029.0e16, 11805.0e17, 2361.0e18,
+                471.0e19, 93.0e20, 17.0e21, 3.0e22, 1801439850948199.0e1,
+                360287970189641.0e2, 72057594037929.0e3, 14411518807587.0e4,
+                2882303761519.0e5, 576460752305.0e6, 115292150461.0e7,
+                23058430093.0e8, 4611686019.0e9, 922337205.0e10,
+                184467441.0e11, 36893489.0e12, 7378699.0e13, 1475741.0e14,
+                295149.0e15, 59031.0e16, 11807.0e17, 2363.0e18, 473.0e19,
+                95.0e20, 19.0e21, 5.0e22, 302230528.0e15, 302232576.0e15,
+                81123342286848.0e18, 81192061763584.0e18],
+    lists:foreach(fun(V) -> g_t(V) end, L_switch),
+
+    L_edge = [123456789012345683968.0, 1.9156918820264798e-56,
+              6.6564021122018745e+264, 4.91e-6, 5.547e-6],
+    lists:foreach(fun(V) -> g_t(V) end, L_edge),
+
+    ok.
+
+i_2_d(Int) ->
+    <<F:64/float>> = <<Int:64/unsigned-integer>>,
+    F.
+
+parts_2_f(S, E, M) ->
+    <<F:64/float>> = <<S:1, E:11, M:52>>,
+    F.
+
 g_misc() -> 
     L_0_308 = lists:seq(0, 308),
     L_0_307 = lists:seq(0, 307),
-- 
2.26.2

openSUSE Build Service is sponsored by