二郎牛仔回复 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).
问题已解决。
代码在这里:
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).
问题已解决。