二郎牛仔回复 json data , float number precision is wrong?

Erlang cowboy reply json data , float number precision is wrong?

代码在这里:

RstJson = rfc4627:encode({obj, [{"age", 45.99}]}),
{ok, Req3} = cowboy_req:reply(200, [{<<"Content-Type">>, <<"application/json;charset=UTF-8">>}], RstJson, Req2)

然后我从前端客户端得到这个错误的数据:

{
  "age": 45.990000000000002
}

浮点数精度已更改! 我该如何解决这个问题?

让我们看看rfc4627生成的JSON:

> io:format("~s~n", [rfc4627:encode({obj, [{"age", 45.99}]})]).
{"age":4.59900000000000019895e+01}

原来 rfc4627 通过调用 float_to_list/1, which uses "scientific" notation with 20 digits of precision. As Per Hedeland noted on the erlang-questions mailing list in November 2007 编码 floating-point 值,这是一个奇怪的选择:

A reasonable question could be why float_to_list/1 generates 20 digits when a 64-bit float (a.k.a. C double), which is what is used internally, only can hold 15-16 worth of them - I don't know off-hand what a 128-bit float would have, but presumably significantly more than 20, so it's not that either. I guess way back in the dark ages, someone thought that 20 was a nice and even number (I hope it wasn't me:-). The 6.30000 form is of course just the ~p/~w formatting.


然而,事实证明这其实不是问题所在!其实45.990000000000002等于45.99,所以你在前端得到正确的值:

> 45.990000000000002 =:= 45.99.
true

如上所述,64 位浮点数可以容纳 15 或 16 位有效数字,但 45.990000000000002 包含 17 位数字(数一数!)。看起来你的前端试图以比它实际包含的更精确的方式打印数字,从而使数字 看起来 不同,即使它实际上是相同的数字。


考虑到计算机如何处理浮点值,问题 Is floating point math broken? 的答案更详细地说明了为什么这实际上有意义。

rfc4627中编码浮点数函数为:

encode_number(Num, Acc) when is_float(Num) ->
  lists:reverse(float_to_list(Num), Acc).

我改成这样:

encode_number(Num, Acc) when is_float(Num) ->
  lists:reverse(io_lib:format("~p",[Num]), Acc).

问题已解决。