PageRenderTime 137ms CodeModel.GetById 123ms RepoModel.GetById 0ms app.codeStats 1ms

/src/mochinum.erl

http://github.com/basho/mochiweb
Erlang | 372 lines | 267 code | 32 blank | 73 comment | 1 complexity | f05c2bd985a35220ab789b29cdc41d21 MD5 | raw file
Possible License(s): MIT
  1. %% @copyright 2007 Mochi Media, Inc.
  2. %% @author Bob Ippolito <bob@mochimedia.com>
  3. %%
  4. %% Permission is hereby granted, free of charge, to any person obtaining a
  5. %% copy of this software and associated documentation files (the "Software"),
  6. %% to deal in the Software without restriction, including without limitation
  7. %% the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. %% and/or sell copies of the Software, and to permit persons to whom the
  9. %% Software is furnished to do so, subject to the following conditions:
  10. %%
  11. %% The above copyright notice and this permission notice shall be included in
  12. %% all copies or substantial portions of the Software.
  13. %%
  14. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  17. %% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. %% DEALINGS IN THE SOFTWARE.
  21. %% @doc Useful numeric algorithms for floats that cover some deficiencies
  22. %% in the math module. More interesting is digits/1, which implements
  23. %% the algorithm from:
  24. %% http://www.cs.indiana.edu/~burger/fp/index.html
  25. %% See also "Printing Floating-Point Numbers Quickly and Accurately"
  26. %% in Proceedings of the SIGPLAN '96 Conference on Programming Language
  27. %% Design and Implementation.
  28. -module(mochinum).
  29. -author("Bob Ippolito <bob@mochimedia.com>").
  30. -export([digits/1, frexp/1, int_pow/2, int_ceil/1]).
  31. %% IEEE 754 Float exponent bias
  32. -define(FLOAT_BIAS, 1022).
  33. -define(MIN_EXP, -1074).
  34. -define(BIG_POW, 4503599627370496).
  35. %% External API
  36. %% @spec digits(number()) -> string()
  37. %% @doc Returns a string that accurately represents the given integer or float
  38. %% using a conservative amount of digits. Great for generating
  39. %% human-readable output, or compact ASCII serializations for floats.
  40. digits(N) when is_integer(N) ->
  41. integer_to_list(N);
  42. digits(0.0) ->
  43. "0.0";
  44. digits(Float) ->
  45. {Frac1, Exp1} = frexp_int(Float),
  46. [Place0 | Digits0] = digits1(Float, Exp1, Frac1),
  47. {Place, Digits} = transform_digits(Place0, Digits0),
  48. R = insert_decimal(Place, Digits),
  49. case Float < 0 of
  50. true ->
  51. [$- | R];
  52. _ ->
  53. R
  54. end.
  55. %% @spec frexp(F::float()) -> {Frac::float(), Exp::float()}
  56. %% @doc Return the fractional and exponent part of an IEEE 754 double,
  57. %% equivalent to the libc function of the same name.
  58. %% F = Frac * pow(2, Exp).
  59. frexp(F) ->
  60. frexp1(unpack(F)).
  61. %% @spec int_pow(X::integer(), N::integer()) -> Y::integer()
  62. %% @doc Moderately efficient way to exponentiate integers.
  63. %% int_pow(10, 2) = 100.
  64. int_pow(_X, 0) ->
  65. 1;
  66. int_pow(X, N) when N > 0 ->
  67. int_pow(X, N, 1).
  68. %% @spec int_ceil(F::float()) -> integer()
  69. %% @doc Return the ceiling of F as an integer. The ceiling is defined as
  70. %% F when F == trunc(F);
  71. %% trunc(F) when F &lt; 0;
  72. %% trunc(F) + 1 when F &gt; 0.
  73. int_ceil(X) ->
  74. T = trunc(X),
  75. case (X - T) of
  76. Pos when Pos > 0 -> T + 1;
  77. _ -> T
  78. end.
  79. %% Internal API
  80. int_pow(X, N, R) when N < 2 ->
  81. R * X;
  82. int_pow(X, N, R) ->
  83. int_pow(X * X, N bsr 1, case N band 1 of 1 -> R * X; 0 -> R end).
  84. insert_decimal(0, S) ->
  85. "0." ++ S;
  86. insert_decimal(Place, S) when Place > 0 ->
  87. L = length(S),
  88. case Place - L of
  89. 0 ->
  90. S ++ ".0";
  91. N when N < 0 ->
  92. {S0, S1} = lists:split(L + N, S),
  93. S0 ++ "." ++ S1;
  94. N when N < 6 ->
  95. %% More places than digits
  96. S ++ lists:duplicate(N, $0) ++ ".0";
  97. _ ->
  98. insert_decimal_exp(Place, S)
  99. end;
  100. insert_decimal(Place, S) when Place > -6 ->
  101. "0." ++ lists:duplicate(abs(Place), $0) ++ S;
  102. insert_decimal(Place, S) ->
  103. insert_decimal_exp(Place, S).
  104. insert_decimal_exp(Place, S) ->
  105. [C | S0] = S,
  106. S1 = case S0 of
  107. [] ->
  108. "0";
  109. _ ->
  110. S0
  111. end,
  112. Exp = case Place < 0 of
  113. true ->
  114. "e-";
  115. false ->
  116. "e+"
  117. end,
  118. [C] ++ "." ++ S1 ++ Exp ++ integer_to_list(abs(Place - 1)).
  119. digits1(Float, Exp, Frac) ->
  120. Round = ((Frac band 1) =:= 0),
  121. case Exp >= 0 of
  122. true ->
  123. BExp = 1 bsl Exp,
  124. case (Frac =/= ?BIG_POW) of
  125. true ->
  126. scale((Frac * BExp * 2), 2, BExp, BExp,
  127. Round, Round, Float);
  128. false ->
  129. scale((Frac * BExp * 4), 4, (BExp * 2), BExp,
  130. Round, Round, Float)
  131. end;
  132. false ->
  133. case (Exp =:= ?MIN_EXP) orelse (Frac =/= ?BIG_POW) of
  134. true ->
  135. scale((Frac * 2), 1 bsl (1 - Exp), 1, 1,
  136. Round, Round, Float);
  137. false ->
  138. scale((Frac * 4), 1 bsl (2 - Exp), 2, 1,
  139. Round, Round, Float)
  140. end
  141. end.
  142. scale(R, S, MPlus, MMinus, LowOk, HighOk, Float) ->
  143. Est = int_ceil(math:log10(abs(Float)) - 1.0e-10),
  144. %% Note that the scheme implementation uses a 326 element look-up table
  145. %% for int_pow(10, N) where we do not.
  146. case Est >= 0 of
  147. true ->
  148. fixup(R, S * int_pow(10, Est), MPlus, MMinus, Est,
  149. LowOk, HighOk);
  150. false ->
  151. Scale = int_pow(10, -Est),
  152. fixup(R * Scale, S, MPlus * Scale, MMinus * Scale, Est,
  153. LowOk, HighOk)
  154. end.
  155. fixup(R, S, MPlus, MMinus, K, LowOk, HighOk) ->
  156. TooLow = case HighOk of
  157. true ->
  158. (R + MPlus) >= S;
  159. false ->
  160. (R + MPlus) > S
  161. end,
  162. case TooLow of
  163. true ->
  164. [(K + 1) | generate(R, S, MPlus, MMinus, LowOk, HighOk)];
  165. false ->
  166. [K | generate(R * 10, S, MPlus * 10, MMinus * 10, LowOk, HighOk)]
  167. end.
  168. generate(R0, S, MPlus, MMinus, LowOk, HighOk) ->
  169. D = R0 div S,
  170. R = R0 rem S,
  171. TC1 = case LowOk of
  172. true ->
  173. R =< MMinus;
  174. false ->
  175. R < MMinus
  176. end,
  177. TC2 = case HighOk of
  178. true ->
  179. (R + MPlus) >= S;
  180. false ->
  181. (R + MPlus) > S
  182. end,
  183. case TC1 of
  184. false ->
  185. case TC2 of
  186. false ->
  187. [D | generate(R * 10, S, MPlus * 10, MMinus * 10,
  188. LowOk, HighOk)];
  189. true ->
  190. [D + 1]
  191. end;
  192. true ->
  193. case TC2 of
  194. false ->
  195. [D];
  196. true ->
  197. case R * 2 < S of
  198. true ->
  199. [D];
  200. false ->
  201. [D + 1]
  202. end
  203. end
  204. end.
  205. unpack(Float) ->
  206. <<Sign:1, Exp:11, Frac:52>> = <<Float:64/float>>,
  207. {Sign, Exp, Frac}.
  208. frexp1({_Sign, 0, 0}) ->
  209. {0.0, 0};
  210. frexp1({Sign, 0, Frac}) ->
  211. Exp = log2floor(Frac),
  212. <<Frac1:64/float>> = <<Sign:1, ?FLOAT_BIAS:11, (Frac-1):52>>,
  213. {Frac1, -(?FLOAT_BIAS) - 52 + Exp};
  214. frexp1({Sign, Exp, Frac}) ->
  215. <<Frac1:64/float>> = <<Sign:1, ?FLOAT_BIAS:11, Frac:52>>,
  216. {Frac1, Exp - ?FLOAT_BIAS}.
  217. log2floor(Int) ->
  218. log2floor(Int, 0).
  219. log2floor(0, N) ->
  220. N;
  221. log2floor(Int, N) ->
  222. log2floor(Int bsr 1, 1 + N).
  223. transform_digits(Place, [0 | Rest]) ->
  224. transform_digits(Place, Rest);
  225. transform_digits(Place, Digits) ->
  226. {Place, [$0 + D || D <- Digits]}.
  227. frexp_int(F) ->
  228. case unpack(F) of
  229. {_Sign, 0, Frac} ->
  230. {Frac, ?MIN_EXP};
  231. {_Sign, Exp, Frac} ->
  232. {Frac + (1 bsl 52), Exp - 53 - ?FLOAT_BIAS}
  233. end.
  234. %%
  235. %% Tests
  236. %%
  237. -ifdef(TEST).
  238. -include_lib("eunit/include/eunit.hrl").
  239. int_ceil_test() ->
  240. ?assertEqual(1, int_ceil(0.0001)),
  241. ?assertEqual(0, int_ceil(0.0)),
  242. ?assertEqual(1, int_ceil(0.99)),
  243. ?assertEqual(1, int_ceil(1.0)),
  244. ?assertEqual(-1, int_ceil(-1.5)),
  245. ?assertEqual(-2, int_ceil(-2.0)),
  246. ok.
  247. int_pow_test() ->
  248. ?assertEqual(1, int_pow(1, 1)),
  249. ?assertEqual(1, int_pow(1, 0)),
  250. ?assertEqual(1, int_pow(10, 0)),
  251. ?assertEqual(10, int_pow(10, 1)),
  252. ?assertEqual(100, int_pow(10, 2)),
  253. ?assertEqual(1000, int_pow(10, 3)),
  254. ok.
  255. digits_test() ->
  256. ?assertEqual("0",
  257. digits(0)),
  258. ?assertEqual("0.0",
  259. digits(0.0)),
  260. ?assertEqual("1.0",
  261. digits(1.0)),
  262. ?assertEqual("-1.0",
  263. digits(-1.0)),
  264. ?assertEqual("0.1",
  265. digits(0.1)),
  266. ?assertEqual("0.01",
  267. digits(0.01)),
  268. ?assertEqual("0.001",
  269. digits(0.001)),
  270. ?assertEqual("1.0e+6",
  271. digits(1000000.0)),
  272. ?assertEqual("0.5",
  273. digits(0.5)),
  274. ?assertEqual("4503599627370496.0",
  275. digits(4503599627370496.0)),
  276. %% small denormalized number
  277. %% 4.94065645841246544177e-324 =:= 5.0e-324
  278. <<SmallDenorm/float>> = <<0,0,0,0,0,0,0,1>>,
  279. ?assertEqual("5.0e-324",
  280. digits(SmallDenorm)),
  281. ?assertEqual(SmallDenorm,
  282. list_to_float(digits(SmallDenorm))),
  283. %% large denormalized number
  284. %% 2.22507385850720088902e-308
  285. <<BigDenorm/float>> = <<0,15,255,255,255,255,255,255>>,
  286. ?assertEqual("2.225073858507201e-308",
  287. digits(BigDenorm)),
  288. ?assertEqual(BigDenorm,
  289. list_to_float(digits(BigDenorm))),
  290. %% small normalized number
  291. %% 2.22507385850720138309e-308
  292. <<SmallNorm/float>> = <<0,16,0,0,0,0,0,0>>,
  293. ?assertEqual("2.2250738585072014e-308",
  294. digits(SmallNorm)),
  295. ?assertEqual(SmallNorm,
  296. list_to_float(digits(SmallNorm))),
  297. %% large normalized number
  298. %% 1.79769313486231570815e+308
  299. <<LargeNorm/float>> = <<127,239,255,255,255,255,255,255>>,
  300. ?assertEqual("1.7976931348623157e+308",
  301. digits(LargeNorm)),
  302. ?assertEqual(LargeNorm,
  303. list_to_float(digits(LargeNorm))),
  304. %% issue #10 - mochinum:frexp(math:pow(2, -1074)).
  305. ?assertEqual("5.0e-324",
  306. digits(math:pow(2, -1074))),
  307. ok.
  308. frexp_test() ->
  309. %% zero
  310. ?assertEqual({0.0, 0}, frexp(0.0)),
  311. %% one
  312. ?assertEqual({0.5, 1}, frexp(1.0)),
  313. %% negative one
  314. ?assertEqual({-0.5, 1}, frexp(-1.0)),
  315. %% small denormalized number
  316. %% 4.94065645841246544177e-324
  317. <<SmallDenorm/float>> = <<0,0,0,0,0,0,0,1>>,
  318. ?assertEqual({0.5, -1073}, frexp(SmallDenorm)),
  319. %% large denormalized number
  320. %% 2.22507385850720088902e-308
  321. <<BigDenorm/float>> = <<0,15,255,255,255,255,255,255>>,
  322. ?assertEqual(
  323. {0.99999999999999978, -1022},
  324. frexp(BigDenorm)),
  325. %% small normalized number
  326. %% 2.22507385850720138309e-308
  327. <<SmallNorm/float>> = <<0,16,0,0,0,0,0,0>>,
  328. ?assertEqual({0.5, -1021}, frexp(SmallNorm)),
  329. %% large normalized number
  330. %% 1.79769313486231570815e+308
  331. <<LargeNorm/float>> = <<127,239,255,255,255,255,255,255>>,
  332. ?assertEqual(
  333. {0.99999999999999989, 1024},
  334. frexp(LargeNorm)),
  335. %% issue #10 - mochinum:frexp(math:pow(2, -1074)).
  336. ?assertEqual(
  337. {0.5, -1073},
  338. frexp(math:pow(2, -1074))),
  339. ok.
  340. -endif.