浮点数精度

13 浏览
0 Comments

浮点数精度

考虑以下代码:\n

0.1 + 0.2 == 0.3  ->  false

\n

0.1 + 0.2         ->  0.30000000000000004

\n为什么会出现这些不准确的结果?

0
0 Comments

浮点数精度问题的原因是,二进制浮点数无法精确地表示某些十进制小数,就像十进制中无法精确表示1/3一样。在浮点数中,0.1无法用二进制表示为一个“十进制”值,小数点后面会一直有重复的模式。因此,浮点数的值不是精确的,因此无法使用常规的浮点数方法进行精确计算。虽然有其他数值也存在这个问题。

解决方法之一是使用二进制编码的十进制(BCD)或其他形式的十进制数。然而,这些方法的速度较慢(非常慢),并且需要更多的存储空间,而使用二进制浮点数则不同。例如,BCD可以在一个字节中存储2个十进制数位,而字节实际上可以存储256个可能的值,即100/256,浪费了字节的60%可能值。

对于基本的算术运算,x86兼容的CPU仍然有专门的BCD指令,所以它们可能并不那么慢。当然,未压缩的BCD需要8个比特位来存储一个十进制位。

然而,对于浮点数运算,基于BCD的数学比本机二进制浮点数慢上百倍。

可能系统将0.1视为1e-1会更合理吗?也许这会在后续处理中引起更多问题。

事实上,有一些方法可以得到精确的十进制值——用于加法和减法。但对于除法、乘法等运算,它们与二进制方法有相同的问题。这就是为什么会在会计中使用BCD,因为会计主要涉及加减,而且不能计算比一分钱更小的金额。然而,像1/3*3 == 1这样简单的运算在BCD数学中失败(计算结果为假),就像在纸上使用十进制除法一样。

确实,注意我说的是“精确的十进制值”,而不是精确的值。十进制也有无法精确表示的数字(任何分母中有2或5以外的素数因子的分数,2的平方根,π等)。

BCD比二进制浮点数慢得多,没有争议。BCD机器指令比二进制浮点数指令慢得多。可以查阅相关资料。

如果在FPGA中实现BCD,那些认为你会陷入困境的人将自己陷入困境,因为BCD的速度不会更慢。

0
0 Comments

浮点数精度问题是由于硬件设计和浮点数计算算法的限制导致的。硬件设计上,大多数浮点运算都会有一定的误差,因为计算浮点数的硬件只需要保证最后一位的误差小于一个单位的一半。这就意味着,对于单个操作来说,硬件只需要保证误差小于一个单位的一半,这在浮点数除法中尤其有问题。由于每个操作所需的操作数不同,所以无法保证重复的操作结果会产生可接受的误差,因为误差会随着时间的推移而累积。

标准方面,大多数处理器遵循IEEE-754标准,但也有一些采用非规范化或不同的标准。IEEE-754标准允许硬件设计者选择任何小于最后一位单位的一半的误差/epsilon值,并且结果只需要对于一个操作来说小于最后一位单位的一半。这就解释了为什么重复操作会导致误差累积。对于IEEE-754双精度浮点数来说,这是第54位,因为有53位用于表示浮点数的数值部分(规范化),也称为尾数。浮点数运算的其他操作中出现舍入误差的原因是截断的不同模式。IEEE-754允许截断模式包括截断、向零舍入、四舍五入(默认)、向下舍入和向上舍入。所有这些模式都会引入误差,但误差的累积对于指数运算尤其有问题,因为指数运算涉及到重复的乘法。

由于硬件只需要在单个操作中产生一个误差小于最后一位单位的一半的结果,所以如果不加以纠正,浮点数计算中的误差在重复操作中会不断累积。为了解决这个问题,数学家们使用了一些方法,如使用IEEE-754中默认的四舍五入模式,因为随着时间的推移,误差更有可能相互抵消。另外,使用区间算法结合IEEE-754的各种舍入模式来预测舍入误差并进行纠正也是常见的做法。需要注意的是,默认的四舍五入模式保证了单个操作的误差小于最后一位单位的一半。截断、向上舍入和向下舍入模式可能导致大于最后一位单位的一半但小于一个单位的误差,因此除非在区间算术中使用,否则不建议使用这些模式。

总之,浮点数运算中的误差问题根源在于硬件设计的截断和除法中的倒数截断。由于IEEE-754标准只要求单个操作的误差小于最后一位单位的一半,所以在重复操作中,浮点数的误差会不断累积,除非进行纠正。

0
0 Comments

浮点数精度问题的原因是基于IEEE 754标准,大多数编程语言使用这个标准。问题的核心在于,数字在这个格式中表示为一个整数乘以二的幂;分母不是二的幂的有理数(例如0.1,即1/10)无法被准确表示。

对于标准的二进制64位浮点数格式中的0.1,其表示可以准确写为:

- 十进制表示为0.1000000000000000055511151231257827021181583404541015625

- 十六进制表示为0x1.999999999999ap-4

相比之下,有理数0.1,即1/10,可以准确写为:

- 十进制表示为0.1

- 十六进制表示为0x1.99999999999999...p-4,其中的...表示一个无限序列的9

你的程序中的常量0.2和0.3也会是它们真实值的近似值。碰巧的是,最接近0.2的double值比有理数0.2要大,而最接近0.3的double值比有理数0.3要小。0.1和0.2的和最终会比有理数0.3要大,因此与代码中的常量不一致。

解决浮点数精度问题可以采用以下方法:

- 使用舍入函数将浮点数四舍五入到所需的小数位数之前进行显示。

- 使用比较而不是相等测试来允许一定的容差。例如,不要使用if (x == y) {...},而是使用if (abs(x - y) < myToleranceValue) {...},其中abs是绝对值函数,myToleranceValue是选择的容差值。

- 注意,在处理货币时,应使用固定点算术和整数进行计算,因为货币是量化的。

浮点数精度问题不仅存在于二进制浮点数系统中,而且在其他进制的数系统中也存在。十进制数也有类似的问题,这就是为什么像1/3这样的数会变成0.333333333...的原因。实际上,浮点数的精度错误通常会出现,因为我们通常处理的“真实世界”数字经常是十的幂,但这仅仅是因为我们日常使用十进制。这也是为什么我们会说71%,而不是“7分之5”(71%是一个近似值,因为没有任何十进制数能够准确表示5/7)。

总之,二进制浮点数并不是有问题的,它们只是和其他任何进制的数系统一样不完美。

补充说明:在实际应用中,浮点数精度问题意味着在显示之前需要使用舍入函数将浮点数四舍五入到所需的小数位数。还需要使用比较而不是相等测试来允许一定的容差。此外,需要根据具体应用选择合适的容差值,并考虑到由于精度丢失问题可能出现的最大数值。在选择容差值时要注意语言中的“epsilon”常量,它们可以用作容差值,但其有效性取决于所处理的数字的大小,因为使用大数进行计算可能会超过epsilon阈值。

最后,需要注意的是,浮点数运算不仅基于IEEE 754标准。仍然有一些使用旧的IBM十六进制浮点数的系统,还有一些不支持IEEE 754算术的图形卡。然而,大致上可以认为浮点数运算基于IEEE 754标准。

,浮点数精度问题是由于使用基于二进制的浮点数表示法,而我们通常处理的数字是十的幂,导致无法准确表示某些有理数。为了解决这个问题,我们可以使用舍入函数进行四舍五入,并使用比较而不是相等测试来允许一定的容差。同时,在货币计算等场景中,应使用固定点算术和整数进行计算。

0