最近学习rust的时候,了解到rust的浮点数实现是和js是一样的, 也就导致了我们在js上遇到的精度问题, 在rust同样也能遇到.

首先我们来理清, rust的默认浮点类型是f64, 而js由于和其他语言不同, 无论是整数和浮点数都是number类型, 也是64位固定长度, 也就是标准的双精度浮点数,

双精度浮点数(double)是计算机使用的一种数据类型,使用 64 位(8字节) 来存储一个浮点数。 它可以表示十进制的15或16位有效数字,其可以表示的数字的绝对值范围大约是:-1.79E+308 ~ +1.79E+308 [1] 。

既然2种语言底层的标准都是一样的, 都是使用了IEEE 754标准中的double精度, 那我们就直接使用大家熟悉的js来做demo.

为什么使用double精度标准

对比单精度标准来说, 虽然double精度占用比单精度高(8byte > 4byte), 这也就间接意味着cpu在处理上, 单精度会有优势, 但是单精度的致命缺陷就是有效数少而且范围也会更小, 总的来说适用性略低, 而且在现代cpu来说, 处理速度上基本是可以忽略不计的.所以在rust中默认的浮点数类型就是f64, 如果有需要就选择f32(单精度)

double精度如何存储

直接从wiki上抄一张图下来

618px-IEEE_754_Double_Floating_Point_Format.svg.png

  • 符号位:1 位 (+, -)
  • 指数:11 位 (次方)
  • 有效位数精度:52 位

运算过程

我们通常会用十进制来表达浮点数, 但是我们rust/js底层都是用二进制实现浮点数类型的, 比如说我们写一句这样的代码:

var a = 0.1;

在我们程序员眼中它可能就是绝对等于0.1的, 但是在内部实现中, 它需要转换为二进制, 但是在二进制中就是无限精度类型, 也就变成了下面这样:

0.1 -> 0.0001 1001 1001 1001...(1100循环)

但是由于我们底层的标准, 有效位数的精度是52位, 我们在做浮点运算的时候, 多余的数字都会被截断, 所以在js从二进制转换为十进制之后, 就不是我们预想的答案了(在一定精度结果是对的)
, 同理在rust/js中我们也不要使用浮点数做比较, 因为是一个危险不受信赖的计算结果, 也希望精度问题能够引起大家重视, 因为有很多危险的事件是由转换精度触发的:

对于Ariane 4火箭的工作代码在Ariane 5中被重新使用,但是Ariane 5更高速的运算引擎在火箭航天计算机中的算法程序中触发了一个bug。该错误存在于将64位浮点数转换为16位带符号整数的程序中。更快的运算引擎导致了Ariane 5中的64位数据要比Ariane 4中更长,直接诱发了溢出条件,最终导致了航天计算机的崩溃。首先501航天飞机的备份计算机崩溃,然后0.05秒之后,主计算机也崩溃了。这些计算机崩溃直接导致了火箭的主要处理器使火箭的运算引擎过载,同时导致火箭在发射40秒后解体破碎。

顺带提一句, rust中对于整型有溢出处理, 在release环境下, 会按照补码循环溢出的规则去解决, 但是这仍然会造成结果不一致的错误.

如何解决

rust: 我不知道咋解决, 我才学rust
js: 大把的精度库, 最流行的方案就是底层使用string了, bignumber.js, 就可以避免浮点数陷阱;

标签: none

仅有一条评论

  1. Zoupers Zoupers

    rust 也有库, 名字就叫 num
    https://crates.io/crates/num

添加新评论