Math 浮点数学坏了吗?
考虑以下代码: 0.1 + 0.2 -> 0.30000000000000004 为什么会出现这些不准确的情况?二进制数学是这样的。在大多数编程语言中,它基于。问题的关键是,数字在这种格式中表示为整数乘以二的幂;有理数,例如0.1,它是分母不是二的幂的1/10,不能精确表示 对于标准binary64格式的0.1,表示形式可以完全按照 0.10000000000000055115123125782702118158340454015625十进制,或 0x1.9999999999AP-4英寸。 相反,有理数0.1(1/10)可以精确地写成 小数点后0.1,或 0x1.999999999999…p-4,类似于C99六进制浮点表示法,其中。。。表示9的无休止序列。 程序中的常数0.2和0.3也是其真实值的近似值。与0.2最接近的双精度比有理数0.2大,但与0.3最接近的双精度比有理数0.3小。0.1和0.2之和最终大于有理数0.3,因此与代码中的常量不一致 需要对浮点算术问题进行相当全面的处理。有关更易于理解的解释,请参阅 旁注:所有的位置基N数系统都有这个问题 普通的十进制10进制数字也有同样的问题,这就是为什么像1/3这样的数字最终会变成0.333 您刚刚偶然发现了一个数字3/10,它恰好很容易用十进制表示,但不适合二进制。从某种程度上来说,它也是双向的:1/16在十进制0.0625中是一个丑陋的数字,但在二进制中,它看起来就像十进制0.0001中的10000次一样整洁**-如果我们在日常生活中习惯使用以2为基数的数字系统,你甚至会看到这个数字,本能地理解你可以通过将某个数字减半来达到目的,一次又一次地减半 **当然,这并不是浮点数字存储在内存中的确切方式,它们使用的是一种科学符号。然而,它确实说明了一点,即二进制浮点精度错误往往会突然出现,因为我们通常感兴趣的现实世界中的数字往往是十的幂,但这仅仅是因为我们每天都使用十进制数字系统。这也是为什么我们会说71%而不是每7个中的5个71%是近似值,因为5/7不能用任何十进制数精确表示 所以不:二进制浮点数并没有被破坏,它们只是碰巧和其他的N进制一样不完美: 旁注:在编程中使用浮动 实际上,这种精度问题意味着您需要使用舍入函数将浮点数舍入到您感兴趣的小数位数,然后再显示它们 您还需要将相等性测试替换为允许一定公差的比较,这意味着: 如果x==y{…},则不执行此操作 相反,如果ABX-yMath 浮点数学坏了吗?,math,language-agnostic,floating-point,floating-accuracy,Math,Language Agnostic,Floating Point,Floating Accuracy,考虑以下代码: 0.1 + 0.2 -> 0.30000000000000004 为什么会出现这些不准确的情况?二进制数学是这样的。在大多数编程语言中,它基于。问题的关键是,数字在这种格式中表示为整数乘以二的幂;有理数,例如0.1,它是分母不是二的幂的1/10,不能精确表示 对于标准binary64格式的0.1,表示形式可以完全按照 0.10000000000000055115123125782702118158340454015625十进制,或 0x1.9999999999
其中abs是绝对值。myToleranceValue需要为您的特定应用程序选择—它与您准备允许的摆动空间有很大关系,并且您将要比较的最大数值可能是由于精度损失问题。在您选择的语言中,请注意epsilon样式常量。这些值不能用作公差值。浮点舍入错误。由于缺少5的基本因子,0.1在基数-2中不能像在基数-10中那样准确地表示。正如1/3在十进制中表示为无限位数,但在基数3中为0.1一样,0.1在基数2中表示为无限位数,而在基数10中不表示为无限位数。计算机没有无限的内存。浮点舍入错误。发件人: 将无限多个实数压缩成有限位数需要近似表示。尽管有无限多个整数,但在大多数程序中,整数计算的结果可以存储在32位中。相反,给定任何固定的位数,大多数实数计算将产生无法用那么多位数精确表示的量。因此,浮点计算的结果必须经常四舍五入,以适应其有限表示形式。这种舍入误差是浮点计算的特征
它的破译方式与你在小学时学习的十进制10进制表示法的破译方式完全相同,只是为了2进制 要理解,请考虑将1/3表示为十进制值。这是不可能做到的!同样,1/10十进制0.1也不能用以2为基数的二进制a精确表示
s是一个十进制值;小数点后的重复模式将永远持续下去。该值不精确,因此不能使用普通浮点方法进行精确的数学运算。 < P>除了其他正确答案外,还可以考虑缩放值以避免浮点运算的问题。 例如:
var result = 1.0 + 2.0; // result === 3.0 returns true
。。。而不是:
var result = 0.1 + 0.2; // result === 0.3 returns false
表达式0.1+0.2===0.3在JavaScript中返回false,但幸运的是浮点整数运算是精确的,因此可以通过缩放避免十进制表示错误
作为一个实际例子,为了避免精度至关重要的浮点问题,建议1将货币处理为表示美分数的整数:2550美分,而不是25.50美元
1道格拉斯·克罗克福德:.我的解决方法:
function add(a, b, precision) {
var x = Math.pow(10, precision || 2);
return (Math.round(a * x) + Math.round(b * x)) / x;
}
精度是指在加法过程中小数点后要保留的位数。是否尝试过管道胶带解决方案 尝试确定错误发生的时间,并用简短的if语句修复它们,这并不完美,但对于某些问题,这是唯一的解决方案,这就是其中之一
if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
else { return n * 0.1 + 0.000000000000001 ;}
我在c语言的一个科学模拟项目中也遇到了同样的问题,我可以告诉你,如果你忽略蝴蝶效应,它会变成一条巨大的巨龙,从硬件设计师的角度咬你
我认为我应该增加硬件设计师的视角,因为我设计和构建浮点硬件。了解错误的来源可能有助于理解软件中正在发生的情况,最终,我希望这有助于解释浮点错误发生的原因,并且随着时间的推移,浮点错误似乎会累积
1.概述
从工程的角度来看,大多数浮点运算都会有一些误差,因为进行浮点运算的硬件最后只需要误差小于一个单位的一半。因此,许多硬件将停止在一个精度上,而该精度仅在单个操作的最后位置产生小于一个单位一半的误差,这在浮点除法中尤其有问题。构成单个操作的内容取决于该单元的操作数。对于大多数情况,它是两个,但有些单元需要3个或更多操作数。因此,无法保证重复操作会导致期望的错误,因为这些错误会随着时间的推移而累积
2.标准
大多数处理器遵循标准,但有些使用非规范化或不同的标准
. 例如,IEEE-754中有一种非规范化模式,允许以精度为代价表示非常小的浮点数。然而,以下将涵盖IEEE-754的标准化模式,这是典型的操作模式
在IEEE-754标准中,允许硬件设计者使用任何误差/ε值,只要其小于最后一个位置一个单元的一半,并且对于一次操作,结果只需小于最后一个位置一个单元的一半。这就解释了为什么在重复操作时,错误会累积起来。对于IEEE-754双精度,这是第54位,因为53位用于表示浮点数的标准化数字部分,也称为尾数,例如5.3e5中的5.3。下一节将更详细地介绍各种浮点操作的硬件错误原因
3.除法中舍入误差的原因
浮点除法误差的主要原因是用来计算商的除法算法。大多数计算机系统使用逆乘法计算除法,主要是Z=X/Y,Z=X*1/Y。除法是迭代计算的,即每个循环计算商数的一些位,直到达到所需的精度,对于IEEE-754来说,最后一位的误差小于一个单位。Y 1/Y的倒数表在慢除法中称为商选择表QST,商选择表的位大小通常是基数的宽度,或每次迭代中计算的商的位数加上几个保护位。对于IEEE-754标准,双精度64位,它将是除法器基数的大小,加上几个保护位k,其中k>=2。例如,一个除法器的一个典型的商选择表,在基数为4的时候计算商的2位,将是2+2=4位加上一些可选位 3.1除法舍入误差:倒数近似值 商选择表中的倒数取决于:慢除法(如SRT除法)或快除法(如Goldschmidt除法);每个条目都会根据除法算法进行修改,以尽量减少可能的错误。然而,在任何情况下,所有的倒数都是实际倒数的近似值,并引入了一些误差因素。都慢 除法和快速除法迭代计算商,即每一步计算商的一些位数,然后从被除数中减去结果,除法重复这些步骤,直到误差小于最后一个单位的一半。慢除法在每个步骤中计算固定数量的商位数,构建成本通常较低,而快除法在每个步骤中计算可变数量的位数,构建成本通常较高。除法最重要的部分是,大多数除法都依赖于倒数近似值的重复乘法,因此容易出错 4.其他操作中的舍入错误:截断 所有操作中舍入误差的另一个原因是IEEE-754允许的最终答案的不同截断模式。有截短,向零取整,向下取整,向上取整。所有方法都会在单个操作的最后位置引入小于一个单位的误差元素。随着时间的推移和重复操作的进行,截断也会累积增加结果误差。这种截断错误在涉及某种形式的重复乘法的幂运算中尤其有问题 5.重复操作 由于进行浮点计算的硬件只需在单个操作的最后一个位置产生误差小于一个单元一半的结果,因此如果不注意,误差会随着重复操作而增加。这就是为什么在需要有界误差的计算中,数学家使用诸如使用IEEE-754最接近的四舍五入的方法,因为随着时间的推移,误差更可能相互抵消,并结合误差的变化来预测四舍五入误差,并对其进行纠正。由于与其他舍入模式相比,其相对误差较低,因此舍入到最后一位最接近的偶数位数是IEEE-754的默认舍入模式 请注意,默认舍入模式“舍入到最近值”保证一次操作的最后一位误差小于一个单位的一半。单独使用截断、向上取整和向下取整可能会导致最后一位的误差大于一个单位的一半,但最后一位的误差小于一个单位,因此不建议使用这些模式,除非在区间算术中使用它们 6.总结
简言之,浮点运算中出现错误的根本原因是硬件中的截断和除法中的倒数截断的结合。由于IEEE-754标准只要求单个操作的最后一位的误差小于一个单位的一半,重复操作的浮点误差将累加,除非纠正。出现这些奇怪的数字是因为计算机使用二进制2数字系统进行计算,而我们使用十进制10
大多数分数不能用二进制或十进制或两者都精确表示。结果-一个四舍五入但精确的数字结果。已经发布了很多好的答案,但我想再添加一个 并非所有数字都可以通过浮点/双精度表示 例如,在IEEE754浮点标准中,数字0.2将以单精度表示为0.20000003 引擎盖下存储实数的模型将浮点数表示为 即使您可以轻松地键入0.2,FLT_基数和DBL_基数也是2;对于使用IEEE二进制浮点运算标准ISO/IEEE Std 754-1985的FPU的计算机,不是10 所以准确地表示这些数字有点困难。即使您在没有任何中间计算的情况下明确指定此变量。这里的大多数答案都是用非常枯燥的技术术语来回答这个问题。我想用正常人都能理解的术语来解释这个问题 想象一下,你正试图把比萨饼切成薄片。你有一个机器人比萨饼切割器,可以把比萨饼切成两半。它可以将整个比萨饼减半,也可以将现有的一片比萨饼减半,但无论如何,减半总是精确的 这个比萨饼切割器的动作非常精细,如果你从一整块比萨饼开始,然后把它切成两半,然后每次继续把最小的一块切成两半,你可以在这块比萨饼太小以至于无法达到高精度之前,把它切成53倍。此时,您不能再将非常薄的切片减半,但必须按原样包含或排除它 现在,你如何将所有的切片以这样的方式进行分割,使之加起来等于比萨饼的十分之一0.1或五分之一0.2?好好想一想,试着解决它。如果你手边有一个神秘的精确比萨饼切割器,你甚至可以试着用一个真正的比萨饼- 当然,大多数有经验的程序员都知道真正的答案,那就是没有办法用th拼凑出比萨饼的十分之一或五分之一 ose切片,不管你切片的多么精细。你可以做一个非常好的近似,如果你把0.1的近似值和0.2的近似值相加,你会得到一个非常好的0.3的近似值,但它仍然只是一个近似值 对于允许您将比萨饼减半53倍的双精度数字,小于或大于0.1的数字分别为0.099999999999167332731531132594682276248931884765625和0.10000000000000055115123125782702118158340451015625。后者比前者更接近于0.1,因此如果输入值为0.1,数值解析器将支持后者 这两个数字之间的差异是我们必须决定包含的最小部分,这会引入一个向上的偏差,或者排除,这会引入一个向下的偏差。最小切片的技术术语是一个 在0.2的情况下,数字都是相同的,只是按2的因子放大。同样,我们赞成略高于0.2的值 请注意,在这两种情况下,0.1和0.2的近似值都有轻微的向上偏移。如果我们加入足够多的这些偏差,它们将使数字越来越远离我们想要的,事实上,在0.1+0.2的情况下,偏差足够大,结果数字不再是最接近0.3的数字 特别是,0.1+0.2实际上是0.100000000000000551151231257827021181583404541015625+0.2000000000011102230246256540423668090908203125=0.30000000000444089209500626169452667236328125,而最接近0.3的数字实际上是0.29999999999999988897537484345957683319091796875 另外,一些编程语言还提供了可以切割的比萨饼切割器。虽然这样的比萨饼切割器并不常见,但如果你有机会使用它,那么你应该在需要精确获得十分之一或五分之一切片的时候使用它
一些统计数据与这个著名的双精度问题有关 当使用0.1到100之间的0.1步长将所有值a+b相加时,我们有约15%的精度误差。请注意,该错误可能会导致稍大或稍小的值。 以下是一些例子:
0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)
0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)
当使用从100到0.1的0.1步长减去a>b的所有值a-b时,我们有约34%的精度误差概率。
以下是一些例子:
0.1 + 0.2 = 0.30000000000000004 (BIGGER)
0.1 + 0.7 = 0.7999999999999999 (SMALLER)
...
1.7 + 1.9 = 3.5999999999999996 (SMALLER)
1.7 + 2.2 = 3.9000000000000004 (BIGGER)
...
3.2 + 3.6 = 6.800000000000001 (BIGGER)
3.2 + 4.4 = 7.6000000000000005 (BIGGER)
0.6 - 0.2 = 0.39999999999999997 (SMALLER)
0.5 - 0.4 = 0.09999999999999998 (SMALLER)
...
2.1 - 0.2 = 1.9000000000000001 (BIGGER)
2.0 - 1.9 = 0.10000000000000009 (BIGGER)
...
100 - 99.9 = 0.09999999999999432 (SMALLER)
100 - 99.8 = 0.20000000000000284 (BIGGER)
*15%和34%确实很大,所以当精度非常重要时,请始终使用BigDecimal。在0.01步中有两个十进制数字,情况会恶化18%和36%。我的答案很长,所以我将其分为三部分。因为这个问题是关于浮点数学的,所以我把重点放在了机器的实际功能上。我还将其指定为双64位精度,但该参数同样适用于任何浮点运算
序言
数字表示表格中的数字
值=-1^s*1.m51m50…m2m1m02*2e-1023
在64位中:
第一位是:1如果数字为负数,则为0,否则为1。
接下来的11位是,这是1023。换句话说,从双精度数字读取指数位后,必须减去1023以获得2的幂。
剩下的52位是or尾数。在尾数中,一个“隐含的”1。由于任何二进制值的最高有效位为1,因此总是忽略2。
1-IEEE 754允许a-+0和-0的概念被区别对待:1/+0是正无穷大;1/-0是负无穷大。对于零值,尾数和指数位均为零。注:零值+0和-0未明确归类为非规范化2
2-对于偏移指数为零且隐含为0的,情况并非如此。。非规范双精度数的范围为dmin≤ |x|≤ dmax,其中dmin最小可表示的非零数为2-1023-51≈ 4.94*10-324和dmax尾数完全由1组成的最大非规范数为2-1023+1-2-1023-51≈ 2.225*10-308
将双精度数字转换为二进制
许多在线转换器用于将双精度浮点数转换为二进制数,例如at,但以下是一些示例C代码,用于获得双精度数的IEEE 754表示形式。我用冒号分隔三部分:
开门见山:原始问题
跳到底部的TL;DR版本
提问者问为什么0.1+0.2!=0.3
以二进制形式编写,冒号分隔三个部分,IEEE 754表示值为:
0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010
0.1 + 0.2 => 0.300000000000000044408920985006...
0.3 => 0.299999999999999988897769753748...
请注意,尾数由0011的循环数字组成。这就是为什么计算中存在任何错误的关键所在-0.1、0.2和0.3不能在有限个二进制位中以二进制精确表示,任何超过1/9、1/3或1/7的位都可以以十进制数字精确表示
还要注意,我们可以将指数中的幂减小52和
将二进制表示中的点向右移动52个位置,非常类似于10-3*1.23==10-5*123。然后,这使我们能够将二进制表示形式表示为它以a*2p形式表示的精确值。其中“a”是一个整数
将指数转换为十进制,删除偏移量,并在方括号中重新添加隐含的1,0.1和0.2为:
0.1 => 2^-4 * [1].1001100110011001100110011001100110011001100110011010
0.2 => 2^-3 * [1].1001100110011001100110011001100110011001100110011010
or
0.1 => 2^-56 * 7205759403792794 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125
要添加两个数字,指数必须相同,即:
0.1 => 2^-3 * 0.1100110011001100110011001100110011001100110011001101(0)
0.2 => 2^-3 * 1.1001100110011001100110011001100110011001100110011010
sum = 2^-3 * 10.0110011001100110011001100110011001100110011001100111
or
0.1 => 2^-55 * 3602879701896397 = 0.1000000000000000055511151231257827021181583404541015625
0.2 => 2^-55 * 7205759403792794 = 0.200000000000000011102230246251565404236316680908203125
sum = 2^-55 * 10808639105689191 = 0.3000000000000000166533453693773481063544750213623046875
由于总和的形式不是2n*1。{bbb},我们将指数增加1,并移动十进制二进制点,得到:
sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
= 2^-54 * 5404319552844595.5 = 0.3000000000000000166533453693773481063544750213623046875
尾数中现在有53位,第53位在上面一行的方括号中。IEEE 754的默认值为“四舍五入到最近值”-即,如果数字x落在两个值a和b之间,则选择最低有效位为零的值
a = 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
= 2^-2 * 1.0011001100110011001100110011001100110011001100110011
x = 2^-2 * 1.0011001100110011001100110011001100110011001100110011(1)
b = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
请注意,a和b仅在最后一位不同。。。0011 + 1 = ...0100. 在这种情况下,最低有效位为零的值为b,因此总和为:
sum = 2^-2 * 1.0011001100110011001100110011001100110011001100110100
= 2^-54 * 5404319552844596 = 0.3000000000000000444089209850062616169452667236328125
而0.3的二进制表示为:
0.3 => 2^-2 * 1.0011001100110011001100110011001100110011001100110011
= 2^-54 * 5404319552844595 = 0.299999999999999988897769753748434595763683319091796875
它只不同于0.1和0.2之和乘以2-54的二进制表示
0.1和0.2的二进制表示是IEEE 754允许的数字的最精确表示。由于默认的舍入模式,添加这些表示法会产生一个仅在最低有效位上不同的值
TL;博士
在IEEE 754二进制表示中写入0.1+0.2,用冒号分隔三个部分,并将其与0.3进行比较,这是我将不同的位放在方括号中:
0.1 + 0.2 => 0:01111111101:0011001100110011001100110011001100110011001100110[100]
0.3 => 0:01111111101:0011001100110011001100110011001100110011001100110[011]
转换回十进制后,这些值为:
0.1 => 0:01111111011:1001100110011001100110011001100110011001100110011010
0.2 => 0:01111111100:1001100110011001100110011001100110011001100110011010
0.1 + 0.2 => 0.300000000000000044408920985006...
0.3 => 0.299999999999999988897769753748...
差值正好为2-54,约为5.5511151231258×10-17-与原始值相比,在许多应用中都不显著
比较浮点数的最后几位本质上是危险的,任何读过这本涵盖了答案所有主要部分的名著的人都会知道这一点
大多数计算器都使用附加值来解决这个问题,即0.1+0.2如何得到0.3:最后几位是四舍五入的。鉴于没有人提到这一点 一些高级语言(如Python和Java)附带了克服二进制浮点限制的工具。例如: Python和Java,它们在内部用十进制表示法而不是二进制表示法表示数字。两者都有有限的精度,所以它们仍然容易出错,但是它们用二进制浮点算法解决了最常见的问题 处理金钱时,小数非常好:10美分加20美分总是正好是30美分:
>>> 0.1 + 0.2 == 0.3
False
>>> Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
Python的十进制模块基于
Python和apachecommon的。两者都以分子、分母对的形式表示有理数,它们可能给出比十进制浮点运算更精确的结果
这两种解决方案都不是完美的,特别是如果我们考虑性能,或者如果我们需要非常高的精度,但它们仍然用二进制浮点运算解决了大量问题。可以在数字计算机中实现的浮点运算必须使用实数的近似值和对实数的运算。标准版本的文件长达50多页,并有一个委员会处理其勘误表和进一步完善 这种近似是不同类型近似的混合,每种近似都可以忽略或仔细解释,因为其偏离精确性的具体方式。它还涉及硬件和软件两个层面上的一些明确的例外情况,大多数人在假装没有注意到的情况下径直走过 例如,如果您需要使用数字π的无限精度,而不是它的许多较短的替代项中的一个,那么您应该编写或使用符号数学程序
但是如果你同意这样的观点,有时浮点数学在值和逻辑上是模糊的,错误会很快累积,你可以编写你的需求和测试来考虑这一点,这样,您的代码就可以经常处理FPU中的内容。这个问题的许多重复问题都会询问浮点舍入对特定数字的影响。在实践中,通过查看感兴趣的计算的准确结果,而不是仅仅通过阅读,更容易了解它是如何工作的。有些语言提供了实现这一点的方法,例如在Java中将浮点或双精度转换为BigDecimal 因为这是一个语言不可知的问题,所以它需要语言不可知的工具,例如 将其应用于问题中的数字,视为双倍: 0.1转换为0.100000000000000551151231257827021181583404541015625 0.2转换为0.200000000001110223024625156540236316680908203125 0.3转换为0.29999999999988897769753748434595763683319091796875,并且 > 0.300000000000000004转换为0.300000000004440892989850062616169452667236328125 手动或在十进制计算器(如)中添加前两个数字,显示实际输入的精确和为0.3000000000016653345363773481063544750213623046875 如果将其四舍五入到相当于0.3,则四舍五入误差为0.000000000000000027755756156289135907917022705078125。如果四舍五入等于0.300000000000000004,则四舍五入误差为0.000000000000000027755756156289135907917022705078125。圆形至均匀平局断路器适用 回到浮点转换器,0.300000000004的原始十六进制是3FD3334,它以偶数结尾,因此是正确的结果。否,没有中断,但大多数十进制分数必须近似 总结 浮点运算是精确的,不幸的是,它与我们通常的10进制数字表示法不太匹配,所以我们经常给它输入与我们编写的略有出入的内容 即使是像0.01,0.02,0.03,0.04这样的简单数字。。。0.24不能精确地表示为二元分数。如果你数到0.01、.02、.03…,直到你数到0.25,你才能得到base2中可表示的第一个分数。如果您尝试使用FP,那么您的0.01会稍微偏离,因此将其中25个相加到精确的0.25的唯一方法将需要一个涉及保护位和舍入的长因果链。很难预测,所以我们举手说FP是不精确的,但事实并非如此 我们不断地给FP硬件一些在基数10中看似简单但在基数2中却是重复的分数 这是怎么发生的 当我们写十进制时,每一个分数,特别是每一个终止的十进制数,都是一个有理数的形式 a/2n x 5m 在二进制中,我们只得到2n项,即:
7205759403792794/72057594037927936
a/2n
所以在十进制中,我们不能表示1/3。因为基数10包含2作为素数因子,所以我们可以写为二进制分数的每个数字也可以写为基数10分数。然而,我们写的任何10进制分数都不能用二进制表示。范围为0.01、0.02、0.03。。。0.99,在我们的FP格式中只能表示三个数字:0.25、0.50和0.75,因为它们是1/4、1/2和3/4,所有带有素数因子的数字都只使用2n项
在base10中,我们不能代表1/3。但在二进制中,我们不能做1/10或1/3
因此,虽然每一个二进制分数都可以写成十进制,但事实并非如此。事实上,大多数小数都是以二进制形式重复的
处理它
开发人员通常被指示进行如果您只是在银行计算bean,那么首先使用十进制字符串表示的软件解决方案工作得非常好。但是你不能这样做量子色动力学或空气动力学。计算机中存储的浮点数由两部分组成,一部分是整数,另一部分是以整数为基数乘以整数的指数 如果计算机以10为基数工作,0.1将是1 x 10⁻1,0.2等于2 x 10⁻和0.3将是3 x 10⁻¹. 整数数学简单而精确,所以加上0.1+0.2显然会得到0.3 计算机通常不在10进制下工作,而是在2进制下工作。您仍然可以获得某些值的精确结果,例如0.5是1 x 2⁻1和0.25是1 x 2⁻²,并将其相加,结果为3 x 2⁻²或0.75。没错
问题在于数字可以精确地以10为基数表示,但不能以2为基数。这些数字需要四舍五入到最接近的等效数字。假设采用非常常见的IEEE 64位浮点格式,最接近0.1的数字为3602879701896397 x 2⁻⁵⁵, 最接近0.2的数字是7205759403792794 x 2⁻⁵⁵; 将它们相加,结果为10808639105689191 x 2⁻⁵⁵, 或精确的十进制值0.3000000000044408929898500626169452667236328125。浮点数通常是四舍五入显示的。我是否可以添加;人们总是认为这是一个计算机问题,但如果你用手以10为基数计算,你就不能得到1/3+1/3=2/3=true,除非你有无穷大来加0.333。。。到0.333。。。就像1/10+2/10一样==3/10问题在基数2中,将其截断为0.333+0.333=0.666,并可能将其四舍五入为0.667,这在技术上也是不准确的
三元数和三元数都不是问题——也许有些每只手上有15个手指的比赛会问为什么你的十进制数被打破了……只是为了好玩,我按照标准C99中的定义玩了浮点数的表示,并编写了下面的代码 该代码在3个独立的组中打印浮点的二进制表示
SIGN EXPONENT FRACTION
然后它打印一个和,当以足够的精度求和时,它将显示硬件中真正存在的值
因此,当您写入float x=999…,编译器将在函数xx打印的位表示中转换该数字,从而使函数yy打印的和等于给定的数字
实际上,这个总和只是一个近似值。对于数字99999999,编译器将在浮点的位表示中插入数字100000000
在代码之后,我附加了一个控制台会话,在该会话中,我计算硬件中实际存在的常量减去PI和999999999的项的总和,由编译器插入其中
#include <stdio.h>
#include <limits.h>
void
xx(float *x)
{
unsigned char i = sizeof(*x)*CHAR_BIT-1;
do {
switch (i) {
case 31:
printf("sign:");
break;
case 30:
printf("exponent:");
break;
case 23:
printf("fraction:");
break;
}
char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
printf("%d ", b);
} while (i--);
printf("\n");
}
void
yy(float a)
{
int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
int fraction = ((1<<23)-1)&(*(int*)&a);
int exponent = (255&((*(int*)&a)>>23))-127;
printf(sign?"positive" " ( 1+":"negative" " ( 1+");
unsigned int i = 1<<22;
unsigned int j = 1;
do {
char b=(fraction&i)!=0;
b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
} while (j++, i>>=1);
printf("*2^%d", exponent);
printf("\n");
}
void
main()
{
float x=-3.14;
float y=999999999;
printf("%lu\n", sizeof(x));
xx(&x);
xx(&y);
yy(x);
yy(y);
}
就这样。999999999的值实际上是
999999999.999999446351872
您还可以向bc检查-3.14是否也受到干扰。不要忘记在bc中设置比例因子
显示的总和是硬件内部的值。通过计算得到的值取决于设置的比例。我确实将比例因子设置为15。从数学上讲,以无限的精度,它似乎是100000000。另一种看待这个问题的方式:使用64位来表示数字。因此,无法精确表示超过2**64=18446744073709551616个不同的数字 然而,数学说在0和1之间已经有无限多个小数。IEE 754定义了一种编码方式,可将这64位有效地用于更大的数字空间加上NaN和+/-无穷大,因此精确表示的数字之间存在间隙,填充的数字仅为近似值
不幸的是,0.3处于空白状态。由于该线程在当前浮点实现的一般性讨论中有一些分支,因此我要补充的是,有一些项目正在解决它们的问题 举个例子,它展示了一种称为posit的数字类型及其前身unum,它承诺用更少的位提供更好的精度。如果我的理解是正确的,它也解决了问题中的问题。相当有趣的项目,它背后的人是一位数学家。整个过程都是开源的,有许多实际的C/C++、Python、Julia和C实现。这是因为: 浮点数不能在二进制中精确地表示所有小数 所以就像10/3,在10的基础上,精确地说是3.33。。。重复出现,以同样的方式1/10在二进制中不存在 那又怎样?如何处理?有什么解决办法吗 为了提供最佳解决方案,我可以说我发现了以下方法:
parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3
让我解释一下为什么这是最好的解决方案。
正如上面回答中提到的其他人一样,最好使用现成的Javascript toFixed函数来解决这个问题。但你很可能会遇到一些问题
假设你将两个浮点数相加,比如0.2和0.7,这里是:0.2+0.7=0.899999999999
您的预期结果为0.9,这意味着您需要一个精度为1位数的结果。
所以应该使用0.2+0.7.tofixed1
但是你不能只给toFixed一个特定的参数,因为它取决于给定的数字,例如
0.22 + 0.7 = 0.9199999999999999
在本例中,您需要2位数的精度,所以它应该是toFixed2,那么参数应该是什么来适应每个给定的浮点数
你可以说,在每种情况下都是10:
(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"
该死!9点以后你打算怎么处理那些不需要的零?
现在是将其转换为浮动的时候了,以使其符合您的要求:
parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9
既然您找到了解决方案,最好将其作为如下函数提供:
function floatify(number){
return parseFloat((number).toFixed(10));
}
让我们自己试试:
函数浮点数{
返回parseFloatnumber.toFixed10
;
}
函数相加{
变量number1=+$number1.val;
变量number2=+$number2.val;
var unexpectedResult=编号1+编号2;
var expectedResult=浮动数字1+数字2;
$unexpectedResult.textunexpectedResult;
$expectedResult.textexpectedResult;
}
加总;
输入{
宽度:50px;
}
预期结果{
颜色:绿色;
}
意外结果{
颜色:红色;
}
+
=
预期结果:
意外结果:您可以使用math.isclose函数测试近似相等:
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> 0.1 + 0.2 == 0.3
False
想象一下,在以10为基数的情况下工作,比如说,精确到8位数。你检查一下
1/3 + 2 / 3 == 1
并了解这将返回false。为什么?好吧,我们有真实的数字
1/3=0.333。。。。和2/3=0.666
在小数点后八位截断,我们得到
0.33333333 + 0.66666666 = 0.99999999
当然,它与1.00000000的差值正好是0.00000001
具有固定位数的二进制数的情况与此完全类似。作为实数,我们有
1/10=0.00011001100。。。基地2
及
1/5=0.00110011001。。。基地2
如果我们把这些截短到,比如说,7位,那么我们会得到
0.0001100 + 0.0011001 = 0.0100101
而另一方面,
3/10=0.010011001110011。。。基地2
它被截断为7位,是0.0100110,它们的差值正好是0.0000001
确切的情况稍显微妙,因为这些数字通常以科学记数法存储。例如,我们可以将1/10存储为1.10011*2^-4,这取决于我们为指数和尾数分配了多少位,而不是存储为0.0001100。这会影响计算精度的位数
结果是,由于这些舍入错误,您根本不希望在浮点数上使用==。相反,您可以检查其差值的绝对值是否小于某个固定的小数字。十进制数(如0.1、0.2和0.3)在二进制编码的浮点类型中不能精确表示。0.1和0.2的近似值之和与0.3的近似值不同,因此0.1+0.2==0.3的错误可以在这里更清楚地看到:
包括
int main{
printf0.1+0.2==0.3是%s\n,0.1+0.2==0.3?真:假;
printf0.1是%.23f\n,0.1;
printf0.2是%.23f\n,0.2;
printf0.1+0.2是%.23f\n,0.1+0.2;
printf0.3是%.23f\n,0.3;
printf0.3-0.1+0.2是%g\n,0.3-0.1+0.2;
返回0;
}
输出:
0.1 + 0.2 == 0.3 is false
0.1 is 0.10000000000000000555112
0.2 is 0.20000000000000001110223
0.1 + 0.2 is 0.30000000000000004440892
0.3 is 0.29999999999999998889777
0.3 - (0.1 + 0.2) is -5.55112e-17
为了更可靠地计算这些计算,需要对浮点值使用基于十进制的表示。默认情况下,C标准没有指定此类类型,而是将其指定为a中所述的扩展
例如,您的系统上可能提供了_Decimal32、_Decimal64和_Decimal128类型,这些类型在上支持它们,但在上不支持它们
其实很简单。当你有一个像我们这样的10进制系统时,它只能表示使用基数素因子的分数。10的基本因子是2和5。因此,1/2、1/4、1/5、1/8和1/10都可以清晰地表示,因为分母都使用10的素数因子。相比之下,1/3、1/6和1/7都是重复小数,因为它们的分母使用3或7的素数因子。在二进制或基数2中,唯一的素因子是2。所以你只能清楚地表达分数,它只包含2作为素数因子。在二进制中,1/2、1/4、1/8都可以清晰地表示为小数。而1/5或1/10将是重复的小数。因此,0.1和0.2 1/10和1/5在10进制系统中是干净的小数,在计算机运行的2进制系统中是重复的小数。当你对这些重复的小数进行数学运算时,当你把计算机的基数2二进制数转换成一个更容易让人读懂的基数10时,你会得到剩余的小数
从中,我刚刚看到了有关浮点的有趣问题:
考虑以下结果:
error = (2**53+1) - int(float(2**53+1))
当2**53+1时,我们可以清楚地看到一个断点-在2**53之前一切正常
这是因为双精度二进制:IEEE 754双精度二进制浮点格式:binary64
从Wikipedia页面:
双精度二进制浮点是PC机上常用的格式,尽管其性能和带宽成本较高,但它比单精度浮点的范围更广。与单精度浮点格式一样,与相同大小的整数格式相比,它缺少整数精度。它通常被简单地称为double。IEEE 754标准规定二进制64具有:
符号位:1位
指数:11位
有效精度:53位52显式存储
给定的64位双精度数据(具有给定的偏置指数和52位分数)假设的实际值为
或
感谢@a_guest向我指出这一点。浮点数在硬件上表示 是以2为基数的二进制数的分数。例如,小数点:
0.125
0.001
0.3
具有值1/10+2/100+5/1000,并且以相同方式具有二进制分数:
0.125
0.001
0.3
值为0/2+0/4+1/8。这两个分数的值相同,唯一的区别是第一个是十进制分数,第二个是二进制分数
0.00011001100110011001100110011001100110011001100110011010
不幸的是,大多数十进制分数不能用二进制分数精确表示。因此,一般来说,您给出的浮点数仅近似于要存储在机器中的二进制分数
这个问题在基数10中更容易处理。以分数1/3为例。您可以将其近似为小数:
0.125
0.001
0.3
或者更好
0.33
0.333
或者更好
0.33
0.333
等等。不管你写了多少个小数位,结果永远不会精确到1/3,但它是一个总是更接近的估计值
同样,无论使用多少个2位小数,十进制值0.1都不能精确表示为二进制分数。在基数2中,1/10是以下周期数:
0.0001100110011001100110011001100110011001100110011 ...
停在任何有限的位上,你会得到一个近似值
对于Python,在典型的机器上,53位用于浮点数的精度,因此输入十进制0.1时存储的值是二进制分数
0.00011001100110011001100110011001100110011001100110011010
接近但不完全等于1/10
由于解释器中显示浮点数的方式,很容易忘记存储的值是原始小数的近似值。Python只显示二进制存储值的十进制近似值。如果Python要输出为0.1存储的二进制近似值的真正十进制值,它将输出:
>>> 0.1
0.1000000000000000055511151231257827021181583404541015625
这比大多数人预期的小数位多得多,因此Python会显示一个四舍五入的值以提高可读性:
>>> 0.1
0.1
重要的是要理解,实际上这是一种错觉:存储值不完全是1/10,存储值只是在显示器上四舍五入。当您对这些值执行算术运算时,这一点就会变得明显:
>>> 0.1 + 0.2
0.30000000000000004
这种行为是机器浮点表示的本质所固有的:它不是Python中的bug,也不是代码中的bug。您可以在所有其他语言中观察到相同类型的行为使用硬件支持计算浮点数,尽管有些语言默认情况下,或在所有显示模式下,不要使差异可见
另一个惊喜是与生俱来的。例如,如果您尝试将值2.675四舍五入到小数点后两位,您将得到
>>>第2.675轮,第2轮
2.67
round原语的文档表明它舍入到离零最近的值。由于小数点正好位于2.67和2.68之间的中间位置,因此应该得到2.68的二进制近似值。然而,情况并非如此,因为当十进制分数2.675转换为浮点时,它由一个近似值存储,其精确值为:
2.67499999999999982236431605997495353221893310546875
由于近似值略接近2.67而不是2.68,因此舍入向下
如果您的情况是小数位数向下舍入到一半很重要,那么应该使用十进制模块。顺便说一句,十进制模块还提供了一种方便的方式来查看为任何浮点存储的精确值
>>>从十进制输入十进制
>>>小数点2.675
>>>十进制'2.674999999998223643160599749535322189310546875'
0.1不完全存储在1/10中的另一个结果是十个值的总和0.1的值也不等于1.0:
>>>总和=0.0
>>>对于范围为10的i:
... 总和+=0.1
…总数
0.9999999999999999
二进制浮点数的算法包含了许多这样的惊喜。下面在“表示错误”一节中详细解释了0.1的问题。有关此类意外事件的更完整列表,请参见浮点的危险
诚然,没有简单的答案,但是不要过度怀疑浮动的virtula数字!在Python中,浮点数操作中的错误是由于底层硬件造成的,在大多数机器上,每个操作的错误率不超过1/2**53。对于大多数任务来说,这是非常必要的,但是您应该记住,这些不是十进制操作,并且对浮点数的每个操作都可能会遇到新的错误
尽管存在病理病例,但对于大多数常见的用例,您只需将显示屏上的小数位数向上舍入即可获得预期结果。有关浮动显示方式的详细控制,请参阅str.format方法的格式规范的字符串格式语法
答案的这一部分详细解释了0.1的示例,并展示了如何在0.1上对此类案例进行精确分析
你自己的。我们假设您熟悉浮点数的二进制表示法。术语表示错误意味着大多数十进制分数不能精确地用二进制表示。这就是为什么Python或Perl、C、C++、Java、Fortran和许多其他语言通常不以十进制显示精确结果的主要原因:
>>> 0.1 + 0.2
0.30000000000000004
为什么??1/10和2/10不能精确地用二元分数表示。然而,今天2010年7月的所有机器都遵循IEEE-754浮点数算术标准。大多数平台使用IEEE-754双精度表示Python浮点。双精度IEEE-754使用53位精度,因此在读取时,计算机尝试将0.1转换为形式J/2**N的最接近分数,其中J是一个正好53位的整数。重写:
1/10 ~ = J / (2 ** N)
在:
记住J正好是53位,所以>=2**52,但>>2**52
4503599627370496
>>> 2 ** 53
9007199254740992
>>> 2 ** 56/10
7205759403792793
因此,56是N的唯一可能值,它为J留下了正好53位。因此,J的最佳可能值是这个商,四舍五入:
>>>q,r=divmod 2**56,10
>>>r
6.
由于进位大于10的一半,通过四舍五入获得最佳近似值:
>>> q + 1
7205759403792794
因此,IEEE-754双精度中1/10的最佳近似值为2**56以上,即:
7205759403792794/72057594037927936
注意,由于四舍五入是向上进行的,因此结果实际上略大于1/10;如果我们没有四舍五入,商数会略低于1/10。但在任何情况下都不是十分之一
因此计算机永远看不到1/10:它看到的是上面给出的精确分数,使用IEEE-754中的双精度浮点数的最佳近似值:
>>>. 1 * 2 ** 56
7205759403792794.0
如果我们将这个分数乘以10**30,我们可以观察到这些值它的30个小数位的强大的重量
>>> 7205759403792794 * 10 ** 30 // 2 ** 56
100000000000000005551115123125L
这意味着存储在计算机中的精确值大约等于十进制值0.1000000000000005555115123125。在Python2.7和Python3.1之前的版本中,Python将这些值四舍五入至17位有效小数位,显示“0.100000000000001”。在当前版本的Python中,显示的值是分数尽可能短的值,当转换回二进制时给出完全相同的表示,只显示“0.1”。正常的算术是以10为基数,因此小数表示十分之一、百分之一,等。当你试图用二进制基数2算法表示一个浮点数时,你要处理的是二分之一、四分之一、八分之一等等 在硬件中,浮点存储为整数尾数和指数。尾数表示有效数字。指数类似于科学记数法,但它使用的基数是2而不是10。例如,64.0将用尾数1和指数6表示。0.125表示尾数为1,指数为-3 浮点小数必须加上2的负幂
0.1b = 0.5d
0.01b = 0.25d
0.001b = 0.125d
0.0001b = 0.0625d
0.00001b = 0.03125d
等等
在处理浮点运算时,通常使用错误增量而不是相等运算符。而不是
if(a==b) ...
你会用
delta = 0.0001; // or some arbitrarily small amount
if(a - b > -delta && a - b < delta) ...
我认为一些误差常数比ε更正确,因为没有ε可以在所有情况下使用。在不同的情况下需要使用不同的ε。机器ε几乎从来都不是一个好的常数。并非所有的浮点数学都是基于IEEE[754]标准的。例如,仍有一些使用旧的IBM十六进制FP的系统,还有一些图形卡不支持IEEE-754算法。然而,一个合理的近似值是正确的。克雷放弃了IEEE-754对速度的遵从性。Java也放松了其作为优化的坚持。我认为您应该在这个答案中添加一些东西,关于货币计算应该如何始终,始终使用整数上的定点算法来完成,因为货币是量化的。内部会计计算只需花费很少的一分钱,或者,不管你的最小货币单位是什么——这通常有助于减少将每月29.99美元转换为每日汇率时的舍入误差——但它仍然应该是定点算法。有趣的事实是:这个0.1没有准确地用二进制浮点数表示,导致了一场臭名昭著的灾难,导致28人在战争中丧生第一次伊拉克战争。问题是转换本身是不准确的。16.08 * 100 = 1607.9999999999998. 我们是否必须像16*100+08=1608那样对数字进行拆分和单独转换?这里的解决方案是以整数进行所有计算,然后在这种情况下除以比例100,仅在显示数据时进行四舍五入。这将确保您的计算始终是精确的。只需吹毛求疵一点:整数算术仅在浮点运算中精确到一个双关点。
如果使用Java 7的十六进制浮点表示法时,该数字大于0x1p53=9007199254740992,则该点的ulp为2,因此0x1p53+1向下舍入为0x1p53,0x1p53+3向上舍入为0x1p53+4,因为舍入为偶数:-但当然,如果你的数字小于9万亿,你应该没事-PJason,你应该把结果四舍五入到16.08*100+0.5计算机不需要无限的内存就可以得到0.1+0.2=0.3right@Pacerier当然,他们可以使用两个无界精度整数来表示分数,也可以使用引号表示法。正是二进制或十进制的特殊概念使得这不可能实现——你有一个二进制/十进制数字序列,在其中的某个地方有一个基点。为了得到精确的有理数结果,我们需要一种更好的格式。@Pacerier:二进制和十进制浮点都不能精确地存储1/3或1/13。十进制浮点数类型可以精确地表示形式为M/10^E的值,但在表示大多数其他分数时,其精度不如大小类似的二进制浮点数。在许多应用程序中,对任意分数具有更高的精度比对一些特殊分数具有完美精度更有用。@如果将数字存储为二进制浮点数,这就是答案所在。@chux:二进制和十进制类型之间的精度差不大,但十进制类型的最佳情况与最差情况精度的10:1差异远远大于二进制类型的2:1差异。我很好奇是否有人构建了硬件或编写了软件来高效地在十进制类型中运行,因为这两种类型似乎都不适合在硬件或软件中高效地实现。回答既好又短。重复模式看起来像0.0001100110011…这并不能解释为什么没有使用一个更好的算法,它不会首先转换成二进制文件。因为性能。使用二进制的速度要快几千倍,因为它是机器的固有特性。有些方法可以产生精确的十进制值。BCD二进制编码十进制或其他各种形式的十进制数。但是,与使用二进制浮点相比,这两种方法的速度都慢得多,占用的存储空间也更多。例如,压缩BCD在一个字节中存储2个十进制数字。一个字节中有100个可能的值,实际上可以存储256个可能的值,或者100/256,这浪费了一个字节可能值的60%。@Jacksonkr您仍然在以10为基数思考。计算机是以2为基数的。浮点变量通常具有这种行为。这是由它们在硬件中的存储方式造成的。有关更多信息,请查看.JavaScript将小数视为,这意味着加法等操作可能会出现舍入错误。您可能想看看这篇文章:仅供参考,javascript中的所有数字类型都是IEEE-754双精度。因为javascript使用IEEE 754数学标准,所以它使用64位浮点数。简言之,当进行浮点十进制计算时,这会导致精度错误,因为计算机以2为基数工作,而十进制以10为基数。3是错误的。除法中的舍入误差在最后一位不小于一个单位,但在最后一位最多为半个单位。@gnasher729捕捉正确。使用默认IEEE舍入模式,大多数基本操作在最后一个位置的误差也小于1/2个单位。编辑了解释,并指出,如果用户覆盖默认舍入模式,则错误可能大于1 ulp的1/2,但小于1 ulp。这在嵌入式系统中尤其如此。1浮点数没有错误。每一个浮点值都是真实的。大多数但并非所有的浮点运算都会给出不精确的结果。例如,没有完全等于1.0/10.0的二进制浮点值。另一方面,有些运算(例如1.0+1.0)确实给出了精确的结果。浮点除法中出现错误的主要原因是用于计算商的除法算法,这是一个非常容易误导的说法。对于IEEE-754一致除法,浮点除法中出现错误的唯一原因是无法在结果格式中准确表示结果;无论使用何种算法,都会计算相同的结果。@Matt很抱歉响应太晚。这主要是由于资源/时间问题和权衡。有一种方法可以进行长除法/更“正常”的除法,称为带基数2的SRT除法。然而,这会重复移位并从被除数中减去除数,并占用许多时钟周期,因为它只计算每个时钟周期商的一位。我们使用倒数表,这样我们可以在每个循环中计算更多的商位,并进行有效的性能/速度权衡。例如,Scheme就是一个例子
MPE通过GNU Guile。请看-这些函数将数学保持为分数,最后只进行分割。@FloatingRock实际上,很少有主流编程语言内置有理数。阿恩和我一样是个阴谋家,所以这些事情我们都被宠坏了。@ArneBabenhauserheide我认为值得补充的是,这只适用于有理数。所以,如果你用无理数做一些数学,比如π,你必须把它存储为π的倍数。当然,任何涉及π的计算都不能表示为精确的十进制数。@connexo好的。你将如何编程你的比萨饼旋转器以获得36度?什么是36度?提示:如果你能以精确的方式定义它,你也有一个精确的第十个比萨饼切割器。换句话说,你不可能只有1/360度或1/10 36度的二进制浮点。@connexo同样,每个白痴也不可能把比萨饼旋转36度。人类太容易犯错误,不会做出如此精确的事情。我的答案在发布后不久就被否决了。此后,我做了许多更改,包括在二进制中写入0.1和0.2时显式地注意到重复出现的位,而我在最初的版本中忽略了这一点。如果被否决的选民不太可能看到这一点,你能给我一些反馈,让我改进我的答案吗?我觉得我的答案增加了一些新的东西,因为IEEE 754中对总和的处理方式与其他答案中的处理方式不同。而每一个计算机科学家都应该知道。。。涵盖了一些相同的内容,我的答案专门针对0.1+0.2的情况。舍入到最接近的整数并不是解决所有情况下的比较问题的安全方法。0.4999998和0.500001四舍五入为不同的整数,因此每个四舍五入切点周围都有一个危险区。我知道这些十进制字符串可能不能精确地表示为IEEE二进制浮点。此外,尽管浮点是一种传统格式,但它的设计非常好。我不知道如果现在重新设计,会有什么变化。我对它了解得越多,就越觉得它设计得很好。e、 g.偏置指数意味着连续的二进制浮点具有连续的整数表示,因此您可以在IEEE浮点的二进制表示上使用整数增量或减量实现下一个浮点。此外,你可以将浮点数作为整数进行比较,得到正确的答案,除非它们都是负数,因为符号大小与2的补码不同。我不同意,浮点数应该存储为小数而不是二进制,所有问题都解决了。x/2^n+5^n不应该是x/2^n*5^n吗?@RonenFestinger-1/3呢?既然人类使用十进制数,我看不出有什么好的理由可以解释为什么浮点数在默认情况下不表示为十进制,所以我们可以得到准确的结果。人类使用的基数很多,而不是以10为基数的小数,二进制是我们计算最常用的基数。。“很好的理由”是你不能代表每一个基数中的每一个分数。@RonenFestinger二进制算法很容易在计算机上实现,因为它只需要八个带数字的基本运算:比如$a$,$b$在$0,1$,你只需要知道$\operatorname{xor}a,b$和$\operatorname{cb}a,b$,其中xor为异或,cb为进位,除$a=1=b$外,在所有情况下为$0$,在这种情况下,我们有一个,实际上所有操作的可交换性为您节省$2$的情况,您只需要$6$规则。十进制扩展需要在十进制记数法的情况下存储$10\乘以11$,每个位需要$10$不同的状态,这会浪费进位上的存储。@RonenFestinger-Decimal不太准确。这就是这个答案所说的。对于您选择的任何基,都会有有理数分数,它们给出无限重复的数字序列。为了记录,一些第一台计算机确实使用了10号表示法来表示数字,但是先驱的计算机硬件设计者很快得出结论:基础2要更容易和更有效地实现。对于刚刚编辑的人来说:我认为代码引用适合引用代码。这个答案与语言无关,根本不包含任何引用的代码。数字可以用在英语句子中,但这不会把它们变成代码。这很可能是为什么有人将你的数字格式化为代码——不是为了格式化,而是为了可读性。。。。此外,舍入到偶数指的是二进制表示,而不是十进制表示。例如,请参见或。@WaiHaLee I未对任何十进制数应用奇偶检验,仅对十六进制数进行检验。十六进制数字是偶数且仅当其二进制扩展的最低有效位为零。我完全不理解您的第二段。@我将翻译第二段,因为大多数分数不能用十进制或二进制精确表示。因此,大多数结果将被舍入-尽管它们仍然精确到所用表示中固有的位数。@Mark感谢您的清晰解释,但接下来是问题
这就是为什么在Python3中,0.1+0.4加起来至少等于0.5。另外,在Python 3中使用浮点时,检查相等性的最佳方法是什么?@user2417881 IEEE浮点运算对每个运算都有舍入规则,有时舍入可以产生精确的结果,即使两个数字相差一点。细节太长,无法发表评论,我也不是这方面的专家。正如你在这个答案中看到的,0.5是少数可以用二进制表示的小数之一,但这只是巧合。关于平等测试,请参见。@user2417881您的问题引起了我的兴趣,因此我将其转换为完整的问题和答案:这让我非常头疼。我求12个浮点数的和,然后显示这些数的和和和平均值。使用toFixed可能会修复两个数字的求和,但当求和多个数字时,跳跃是有意义的。@Nuryagdy Mustapayev我不明白您的意图,因为我在您可以求和12个浮点数之前进行了测试,然后对结果使用floatify函数,然后对其执行任何操作,我观察到使用它没有问题。我只是说,在我的情况下,我有大约20个参数和20个公式,每个公式的结果取决于其他公式。这个解决方案没有帮助。所以我们真的必须解析所有计算?我们也可以使用定点。例如,如果美分是最精细的粒度,那么可以使用美分数而不是美元数的整数进行计算。