diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..fff3e66 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "deps/nicefloats"] + path = deps/nicefloats + url = git@github.com:talentdeficit/nicefloats.git diff --git a/deps/nicefloats b/deps/nicefloats new file mode 160000 index 0000000..737ba24 --- /dev/null +++ b/deps/nicefloats @@ -0,0 +1 @@ +Subproject commit 737ba24bb05ebde51dcafc7d36b1b1ea4201c21e diff --git a/rebar.config b/rebar.config index 72fd678..79fe903 100644 --- a/rebar.config +++ b/rebar.config @@ -1,3 +1,9 @@ +{lib_dirs, ["deps"]}. + +{sub_dirs, [ + "deps/nicefloats" +]}. + %% edit eunit_test_path if you want to run your own tests, use "../" not "./" as %% rebar changes to working dir to .eunit when running tests {eunit_compile_opts, [{d, eunit_test_path, "../test/cases"}]}. diff --git a/src/jsx_eep0018.erl b/src/jsx_eep0018.erl index c648f4e..b407c5a 100644 --- a/src/jsx_eep0018.erl +++ b/src/jsx_eep0018.erl @@ -218,7 +218,7 @@ list_to_events([], Acc) -> term_to_event(List) when is_list(List) -> term_to_events(List); term_to_event(Float) when is_float(Float) -> - [{float, nice_decimal(Float)}]; + [{float, nicefloats:format(Float)}]; term_to_event(Integer) when is_integer(Integer) -> [{integer, erlang:integer_to_list(Integer)}]; term_to_event(String) when is_binary(String) -> @@ -250,133 +250,6 @@ encode_key_repeats(Key, [_|Rest], Level) -> encode_key_repeats(_, [], 0) -> false. - -%% conversion of floats to 'nice' decimal output. erlang's float implementation -%% is almost but not quite ieee 754. it converts negative zero to plain zero -%% silently, and throws exceptions for any operations that would produce NaN -%% or infinity. as far as I can tell that is. trying to match against NaN or -%% infinity binary patterns produces nomatch exceptions, and arithmetic -%% operations produce badarg exceptions. with that in mind, this function -%% makes no attempt to handle special values (except for zero) - -%% algorithm from "Printing FLoating-Point Numbers Quickly and Accurately" by -%% Burger & Dybvig -nice_decimal(0.0) -> "0.0"; -nice_decimal(Num) when is_float(Num) -> - {F, E} = extract(<>), - {R, S, MP, MM} = initial_vals(F, E), - K = ceiling(math:log10(abs(Num)) - 1.0e-10), - Round = F band 1 =:= 0, - {Dpoint, Digits} = scale(R, S, MP, MM, K, Round), - if Num >= 0 -> format(Dpoint, Digits) - ; Num < 0 -> "-" ++ format(Dpoint, Digits) - end. - - -extract(<<_:1, 0:11, Frac:52>>) -> {Frac, -1074}; -extract(<<_:1, Exp:11, Frac:52>>) -> {Frac + (1 bsl 52), Exp - 1075}. - - -ceiling(X) -> - Y = trunc(X), - case X - Y of - Z when Z > 0 -> Y + 1 - ; _ -> Y - end. - - -initial_vals(F, E) when E >= 0, F /= 1 bsl 52 -> - BE = 1 bsl E, - {F * BE * 2, 2, BE, BE}; -initial_vals(F, E) when E >= 0 -> - BE = 1 bsl E, - {F * BE * 4, 4, BE * 2, BE}; -initial_vals(F, E) when E == -1074; F /= 1 bsl 52 -> - {F * 2, 1 bsl (-E + 1), 1, 1}; -initial_vals(F, E) -> - {F * 4, 1 bsl (-E + 2), 2, 1}. - - -scale(R, S, MP, MM, K, Round) -> - case K >= 0 of - true -> fixup(R, S * pow(10, K), MP, MM, K, Round) - ; false -> - Scale = pow(10, -1 * K), - fixup(R * Scale, S, MP * Scale, MM * Scale, K, Round) - end. - - -fixup(R, S, MP, MM, K, true) -> - case (R + MP >= S) of - true -> {K + 1, generate(R, S, MP, MM, true)} - ; false -> {K, generate(R * 10, S, MP * 10, MM * 10, true)} - end; -fixup(R, S, MP, MM, K, false) -> - case (R + MP > S) of - true -> {K + 1, generate(R, S, MP, MM, true)} - ; false -> {K, generate(R * 10, S, MP * 10, MM * 10, true)} - end. - - -generate(RT, S, MP, MM, Round) -> - D = RT div S, - R = RT rem S, - TC1 = case Round of true -> (R =< MM); false -> (R < MM) end, - TC2 = case Round of true -> (R + MP >= S); false -> (R + MP > S) end, - case TC1 of - false -> case TC2 of - false -> [D | generate(R * 10, S, MP * 10, MM * 10, Round)] - ; true -> [D + 1] - end - ; true -> case TC2 of - false -> [D] - ; true -> case R * 2 < S of - true -> [D] - ; false -> [D + 1] - end - end - end. - - -%% this is not efficient at all and should be replaced with a lookup table -%% probably -pow(_B, 0) -> 1; -pow(B, E) when E > 0 -> pow(B, E, 1). - -pow(B, E, Acc) when E < 2 -> B * Acc; -pow(B, E, Acc) when E band 1 == 1 -> pow(B * B, E bsr 1, B * Acc); -pow(B, E, Acc) -> pow(B * B, E bsr 1, Acc). - - -format(0, Digits) -> - format(Digits, ignore, ".0"); -format(Dpoint, Digits) when Dpoint =< length(Digits), Dpoint > 0 -> - format(Digits, Dpoint, []); -format(Dpoint, Digits) when Dpoint > 0 -> - Pad = Dpoint - length(Digits), - case Pad of - X when X > 6 -> - format(Digits, 1, []) ++ "e" ++ integer_to_list(Dpoint - 1) - ; _ -> - format(Digits ++ [ 0 || _ <- lists:seq(1, Pad)], Dpoint, []) - end; -format(Dpoint, Digits) when Dpoint < 0 -> - format(Digits, 1, []) ++ "e" ++ integer_to_list(Dpoint - 1). - -format([], 0, Acc) -> - lists:reverse("0." ++ Acc); -format([], ignore, Acc) -> - lists:reverse(Acc); -format(Digits, 0, Acc) -> - format(Digits, ignore, "." ++ Acc); -format([Digit|Digits], Dpoint, Acc) -> - format(Digits, - case Dpoint of ignore -> ignore; X -> X - 1 end, to_ascii(Digit) ++ Acc - ). - - -to_ascii(X) -> [X + 48]. %% ascii "1" is [49], "2" is [50], etc... - %% json string escaping, for utf8 binaries. escape the json control sequences to %% their json equivalent, escape other control characters to \uXXXX sequences, @@ -592,42 +465,5 @@ escape_test_() -> ) } ]. - -nice_decimal_test_() -> - [ - {"0.0", ?_assert(nice_decimal(0.0) =:= "0.0")}, - {"1.0", ?_assert(nice_decimal(1.0) =:= "1.0")}, - {"-1.0", ?_assert(nice_decimal(-1.0) =:= "-1.0")}, - {"3.1234567890987654321", - ?_assert( - nice_decimal(3.1234567890987654321) =:= "3.1234567890987655") - }, - {"1.0e23", ?_assert(nice_decimal(1.0e23) =:= "1.0e23")}, - {"0.3", ?_assert(nice_decimal(3.0/10.0) =:= "0.3")}, - {"0.0001", ?_assert(nice_decimal(0.0001) =:= "1.0e-4")}, - {"0.00000001", ?_assert(nice_decimal(0.00000001) =:= "1.0e-8")}, - {"1.0e-323", ?_assert(nice_decimal(1.0e-323) =:= "1.0e-323")}, - {"1.0e308", ?_assert(nice_decimal(1.0e308) =:= "1.0e308")}, - {"min normalized float", - ?_assert( - nice_decimal(math:pow(2, -1022)) =:= "2.2250738585072014e-308" - ) - }, - {"max normalized float", - ?_assert( - nice_decimal((2 - math:pow(2, -52)) * math:pow(2, 1023)) - =:= "1.7976931348623157e308" - ) - }, - {"min denormalized float", - ?_assert(nice_decimal(math:pow(2, -1074)) =:= "5.0e-324") - }, - {"max denormalized float", - ?_assert( - nice_decimal((1 - math:pow(2, -52)) * math:pow(2, -1022)) - =:= "2.225073858507201e-308" - ) - } - ]. -endif. \ No newline at end of file