From 620e6c7d9186c00b403cf11d2e9e9e19a3881725 Mon Sep 17 00:00:00 2001 From: alisdair sullivan Date: Sun, 4 Mar 2012 17:01:04 -0800 Subject: [PATCH] factor out float formatting into nicedecimal, include as dep --- makefile | 2 +- rebar.config | 7 ++ src/jsx_to_json.erl | 2 +- src/jsx_utils.erl | 169 -------------------------------------------- 4 files changed, 9 insertions(+), 171 deletions(-) diff --git a/makefile b/makefile index 5dccc0f..d6d2f8c 100644 --- a/makefile +++ b/makefile @@ -5,4 +5,4 @@ clean: ./rebar clean test: clean - ./rebar --jobs=1 skip_deps=true eunit \ No newline at end of file + ./rebar --jobs=1 eunit \ No newline at end of file diff --git a/rebar.config b/rebar.config index 81b0407..6303376 100644 --- a/rebar.config +++ b/rebar.config @@ -21,3 +21,10 @@ {dialyzer_opts, [{warnings, [unmatched_returns, error_handling, race_conditions, behaviours]}]}. {xref_checks, [undefined_function_calls]}. {cover_enabled, true}. + +{deps, [ + {'nicedecimal', + ".*", + {git, "git://github.com/talentdeficit/nicedecimal.git"} + } +]}. diff --git a/src/jsx_to_json.erl b/src/jsx_to_json.erl index 5a6dada..e5a80fc 100644 --- a/src/jsx_to_json.erl +++ b/src/jsx_to_json.erl @@ -139,7 +139,7 @@ encode(literal, Literal, _Opts) -> encode(integer, Integer, _Opts) -> erlang:integer_to_list(Integer); encode(float, Float, _Opts) -> - jsx_utils:nice_decimal(Float). + nicedecimal:format(Float). space(Opts) -> diff --git a/src/jsx_utils.erl b/src/jsx_utils.erl index 992736c..b26ff6c 100644 --- a/src/jsx_utils.erl +++ b/src/jsx_utils.erl @@ -24,7 +24,6 @@ -module(jsx_utils). -export([parse_opts/1]). --export([nice_decimal/1]). -export([json_escape/2]). -include("../include/jsx_opts.hrl"). @@ -49,137 +48,6 @@ parse_opts(_, _) -> {error, badarg}. - -%% 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 - --spec nice_decimal(Float::float()) -> string(). - -nice_decimal(0.0) -> "0.0"; -nice_decimal(Num) -> - {F, E} = extract(<>), - {R, S, MP, MM} = initial_vals(F, E), - K = ceiling((math:log(abs(Num)) / math:log(10)) - 1.0e-10), - Round = F band 1 =:= 0, - {Dpoint, Digits} = scale(R, S, MP, MM, K, 10, Round), - if Num >= 0 -> digits_to_list(Dpoint, Digits) - ; Num < 0 -> "-" ++ digits_to_list(Dpoint, Digits) - end. - - -%% internal functions - -extract(<<_:1, 0:11, Frac:52>>) -> {Frac, -1074}; -extract(<<_:1, Exp:11, Frac:52>>) -> {Frac + (1 bsl 52), Exp - 1075}. - - -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}. - - -ceiling(X) -> - Y = erlang:trunc(X), - case X - Y of - Z when Z > 0 -> Y + 1 - ; _ -> Y - end. - - -scale(R, S, MP, MM, K, B, Round) -> - case K >= 0 of - true -> fixup(R, S * pow(B, K), MP, MM, K, B, Round) - ; false -> - Scale = pow(B, -1 * K), - fixup(R * Scale, S, MP * Scale, MM * Scale, K, B, Round) - end. - - -fixup(R, S, MP, MM, K, B, true) -> - case (R + MP >= S) of - true -> {K + 1, generate(R, S, MP, MM, B, true)} - ; false -> {K, generate(R * B, S, MP * B, MM * B, B, true)} - end; -fixup(R, S, MP, MM, K, B, false) -> - case (R + MP > S) of - true -> {K + 1, generate(R, S, MP, MM, B, true)} - ; false -> {K, generate(R * B, S, MP * B, MM * B, B, true)} - end. - - -generate(RT, S, MP, MM, B, 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 * B, S, MP * B, MM * B, B, 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). - - -digits_to_list(0, Digits) -> - digits_to_list(Digits, ignore, ".0"); -digits_to_list(Dpoint, Digits) when Dpoint =< length(Digits), Dpoint > 0 -> - digits_to_list(Digits, Dpoint, []); -digits_to_list(Dpoint, Digits) when Dpoint > 0 -> - Pad = Dpoint - length(Digits), - case Pad of - X when X > 6 -> - digits_to_list(Digits, 1, []) ++ "e" ++ integer_to_list(Dpoint - 1) - ; _ -> - digits_to_list(Digits ++ [ 0 || _ <- lists:seq(1, Pad)], Dpoint, []) - end; -digits_to_list(Dpoint, Digits) when Dpoint < 0 -> - digits_to_list(Digits, 1, []) ++ "e" ++ integer_to_list(Dpoint - 1). - -digits_to_list([], 0, Acc) -> - lists:reverse("0." ++ Acc); -digits_to_list([], ignore, Acc) -> - lists:reverse(Acc); -digits_to_list(Digits, 0, Acc) -> - digits_to_list(Digits, ignore, "." ++ Acc); -digits_to_list([Digit|Digits], Dpoint, Acc) -> - digits_to_list(Digits, - case Dpoint of ignore -> ignore; X -> X - 1 end, [to_hex(Digit)] ++ Acc - ). - - - %% json string escaping, for utf8 binaries. escape the json control sequences to %% their json equivalent, escape other control characters to \uXXXX sequences, %% everything else should be a legal json string component @@ -253,43 +121,6 @@ to_hex(X) -> X + 48. %% ascii "1" is [49], "2" is [50], etc... %% eunit tests -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). - -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" - ) - } - ]. binary_escape_test_() ->