diff --git a/deps/nicefloats/README.markdown b/deps/nicefloats/README.markdown index 78d5b7c..4136729 100644 --- a/deps/nicefloats/README.markdown +++ b/deps/nicefloats/README.markdown @@ -1,5 +1,5 @@ -nicefloats -========== +nicefloats (v0.9.0) +=================== an erlang implementation of nice decimal representations of floating point numbers, based on "Printing FLoating-Point Numbers Quickly and Accurately" by Burger & Dybvig diff --git a/deps/nicefloats/ebin/nicefloats.app b/deps/nicefloats/ebin/nicefloats.app index a05a489..a59f94c 100644 --- a/deps/nicefloats/ebin/nicefloats.app +++ b/deps/nicefloats/ebin/nicefloats.app @@ -1,7 +1,7 @@ {application, nicefloats, [ {description, "'nice' decimal representations of floating point numbers"}, - {vsn, "0.9"}, + {vsn, "0.9.0"}, {modules, [ nicefloats ]}, diff --git a/deps/nicefloats/rebar b/deps/nicefloats/rebar index 823976b..e98b2a0 100755 Binary files a/deps/nicefloats/rebar and b/deps/nicefloats/rebar differ diff --git a/deps/nicefloats/src/nicefloats.erl b/deps/nicefloats/src/nicefloats.erl index 24452c6..9ed5963 100644 --- a/deps/nicefloats/src/nicefloats.erl +++ b/deps/nicefloats/src/nicefloats.erl @@ -1,6 +1,7 @@ %% The MIT License %% Copyright (c) 2010 Alisdair Sullivan +%% 2007 Bob Ippolito %% Permission is hereby granted, free of charge, to any person obtaining a copy %% of this software and associated documentation files (the "Software"), to deal @@ -29,10 +30,14 @@ %% operations produce badarg exceptions. with that in mind, this function %% makes no attempt to handle special values (except for zero) +%% although this is a from scratch implementation of burger & dybvig's algorithm +%% bob ippolito's implementation in mochiweb did provide signifigant guidance +%% and is reflected in the choice of license and copyright attribution + -module(nicefloats). --export([format/1]). +-export([format/1, format/2]). -ifdef(TEST). @@ -40,23 +45,47 @@ -endif. +-record(opts, { + exp_limit = 6, + base = 10 +}). + + -spec format(Float::float()) -> string(). format(Float) when is_float(Float) -> - nice_decimal(Float). + format(Float, []). + +-spec format(Float::float(), OptsList::list()) -> string(). + +format(Float, OptsList) when is_float(Float) -> + nice_decimal(Float, parse_opts(OptsList)). + + +parse_opts(OptsList) -> + parse_opts(OptsList, #opts{}). + +parse_opts([{exp_limit, Val}|Rest], Opts) when is_integer(Val), Val >= 0 -> + parse_opts(Rest, Opts#opts{exp_limit = Val}); +parse_opts([{base, N}|Rest], Opts) when is_integer(N), N >= 2, N =< 16 -> + parse_opts(Rest, Opts#opts{base = N}); +parse_opts([], Opts) -> + Opts; +parse_opts(_, _) -> + {error, badarg}. -%% algorithm from "Printing FLoating-Point Numbers Quickly and Accurately" by +%% algorithm from "Printing Floating-Point Numbers Quickly and Accurately" by %% Burger & Dybvig -nice_decimal(0.0) -> "0.0"; -nice_decimal(Num) -> +nice_decimal(0.0, _) -> "0.0"; +nice_decimal(Num, Opts) -> {F, E} = extract(<>), {R, S, MP, MM} = initial_vals(F, E), - K = ceiling(math:log10(abs(Num)) - 1.0e-10), + K = ceiling((math:log(abs(Num)) / math:log(Opts#opts.base)) - 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) + {Dpoint, Digits} = scale(R, S, MP, MM, K, Opts#opts.base, Round), + if Num >= 0 -> digits_to_list(Dpoint, Digits) + ; Num < 0 -> "-" ++ digits_to_list(Dpoint, Digits) end. @@ -64,14 +93,6 @@ 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}; @@ -84,35 +105,43 @@ initial_vals(F, E) -> {F * 4, 1 bsl (-E + 2), 2, 1}. -scale(R, S, MP, MM, K, Round) -> +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(10, K), MP, MM, K, Round) + true -> fixup(R, S * pow(B, K), MP, MM, K, B, Round) ; false -> - Scale = pow(10, -1 * K), - fixup(R * Scale, S, MP * Scale, MM * Scale, K, Round) + Scale = pow(B, -1 * K), + fixup(R * Scale, S, MP * Scale, MM * Scale, K, B, Round) end. -fixup(R, S, MP, MM, K, true) -> +fixup(R, S, MP, MM, K, B, 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)} + 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, false) -> +fixup(R, S, MP, MM, K, B, 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)} + 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, Round) -> +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 * 10, S, MP * 10, MM * 10, Round)] + false -> [D | generate(R * B, S, MP * B, MM * B, B, Round)] ; true -> [D + 1] end ; true -> case TC2 of @@ -135,33 +164,39 @@ 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 -> +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 -> - format(Digits, 1, []) ++ "e" ++ integer_to_list(Dpoint - 1) + digits_to_list(Digits, 1, []) ++ "e" ++ integer_to_list(Dpoint - 1) ; _ -> - format(Digits ++ [ 0 || _ <- lists:seq(1, Pad)], Dpoint, []) + digits_to_list(Digits ++ [ 0 || _ <- lists:seq(1, Pad)], Dpoint, []) end; -format(Dpoint, Digits) when Dpoint < 0 -> - format(Digits, 1, []) ++ "e" ++ integer_to_list(Dpoint - 1). +digits_to_list(Dpoint, Digits) when Dpoint < 0 -> + digits_to_list(Digits, 1, []) ++ "e" ++ integer_to_list(Dpoint - 1). -format([], 0, Acc) -> +digits_to_list([], 0, Acc) -> lists:reverse("0." ++ Acc); -format([], ignore, Acc) -> +digits_to_list([], ignore, Acc) -> lists:reverse(Acc); -format(Digits, 0, Acc) -> - format(Digits, ignore, "." ++ Acc); -format([Digit|Digits], Dpoint, Acc) -> - format(Digits, +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_ascii(Digit) ++ Acc ). +to_ascii(10) -> "a"; +to_ascii(11) -> "b"; +to_ascii(12) -> "c"; +to_ascii(13) -> "d"; +to_ascii(14) -> "e"; +to_ascii(15) -> "f"; to_ascii(X) -> [X + 48]. %% ascii "1" is [49], "2" is [50], etc... @@ -171,36 +206,36 @@ to_ascii(X) -> [X + 48]. %% ascii "1" is [49], "2" is [50], etc... 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")}, + {"0.0", ?_assert(format(0.0) =:= "0.0")}, + {"1.0", ?_assert(format(1.0) =:= "1.0")}, + {"-1.0", ?_assert(format(-1.0) =:= "-1.0")}, {"3.1234567890987654321", ?_assert( - nice_decimal(3.1234567890987654321) =:= "3.1234567890987655") + format(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")}, + {"1.0e23", ?_assert(format(1.0e23) =:= "1.0e23")}, + {"0.3", ?_assert(format(3.0/10.0) =:= "0.3")}, + {"0.0001", ?_assert(format(0.0001) =:= "1.0e-4")}, + {"0.00000001", ?_assert(format(0.00000001) =:= "1.0e-8")}, + {"1.0e-323", ?_assert(format(1.0e-323) =:= "1.0e-323")}, + {"1.0e308", ?_assert(format(1.0e308) =:= "1.0e308")}, {"min normalized float", ?_assert( - nice_decimal(math:pow(2, -1022)) =:= "2.2250738585072014e-308" + format(math:pow(2, -1022)) =:= "2.2250738585072014e-308" ) }, {"max normalized float", ?_assert( - nice_decimal((2 - math:pow(2, -52)) * math:pow(2, 1023)) + format((2 - math:pow(2, -52)) * math:pow(2, 1023)) =:= "1.7976931348623157e308" ) }, {"min denormalized float", - ?_assert(nice_decimal(math:pow(2, -1074)) =:= "5.0e-324") + ?_assert(format(math:pow(2, -1074)) =:= "5.0e-324") }, {"max denormalized float", ?_assert( - nice_decimal((1 - math:pow(2, -52)) * math:pow(2, -1022)) + format((1 - math:pow(2, -52)) * math:pow(2, -1022)) =:= "2.225073858507201e-308" ) }