在构建处理大量数学计算的应用程序时,我遇到了某些数字会导致舍入错误的问题。
虽然我知道浮点数不是精确的,但问题是我如何处理确切的数字,以确保在对它们进行计算时,浮点数舍入不会引起任何问题?
distanceTraveled(startVel, duration, acceleration)
。
在构建处理大量数学计算的应用程序时,我遇到了某些数字会导致舍入错误的问题。
虽然我知道浮点数不是精确的,但问题是我如何处理确切的数字,以确保在对它们进行计算时,浮点数舍入不会引起任何问题?
distanceTraveled(startVel, duration, acceleration)
。
Answers:
创建可替代浮点数舍入的数字类型的三种基本方法。这些的共同主题是它们以各种方式使用整数数学。
合理性
用分子和分母表示作为一个整体的数字和有理数。该数字15.589
将表示为w: 15; n: 589; d:1000
。
当加到0.25(即w: 0; n: 1; d: 4
)时,这涉及计算LCM,然后将两个数字相加。这在许多情况下效果很好,但是当您使用彼此互质的许多有理数时会产生非常大的数。
固定点
您有整个部分,还有小数部分。所有数字均以该精度四舍五入(有一个字-但您知道它在哪里)。例如,您可以有3个小数点的不动点。 15.589
+ 0.250
变成589 + 250 % 1000
小数部分的加法(然后是整个整数的任何进位)。这对于现有数据库非常有效。如前所述,这里有四舍五入,但是您知道它的位置,并且可以指定它的精度,使其比所需的精度更高(您只测量到3个小数点,因此将其固定为4)。
浮动定点
存储值和精度。 15.589
存储为15589
值和3
精度,而0.25
存储为25
和2
。这可以处理任意精度。我相信这是Java BigDecimal的内部用法(最近没有查看过)的用途。在某个时候,您将需要使它退回到这种格式并显示出来-这可能涉及四舍五入(同样,您可以控制它的位置)。
确定表示形式的选择后,您可以找到使用该表示形式的现有第三方库,也可以编写自己的库。编写自己的代码时,请确保对其进行单元测试,并确保正确进行数学运算。
如果浮点值存在舍入问题,而您又不想遇到舍入问题,那么从逻辑上讲,唯一的做法是不使用浮点值。
现在的问题变成:“我该如何进行不包含浮点变量的非整数值的数学运算?” 答案是任意精度的数据类型。计算较慢,因为它们必须用软件而不是硬件来实现,但它们是准确的。您没有说使用什么语言,所以我不推荐一个软件包,但是对于大多数流行的编程语言都有任意的精度库。
lot of mathematical calculations
没有帮助,也没有给出答案。在绝大多数情况下(如果您不使用货币),那么浮动就足够了。
浮点算术通常非常精确(a的15个十进制数字double
)并且非常灵活。当您在做数学运算时,这些问题就会浮出水面,从而大大降低了精度位数。这里有些例子:
减法取消:1234567890.12345 - 1234567890.12300
,结果0.0045
只有两位十进制数字的精度。每当您减去两个相似幅度的数字时,就会发出警告。
吞没精度:1234567890.12345 + 0.123456789012345
计算为1234567890.24691
,第二个操作数的最后十位丢失。
乘法:如果将两个15位数字相乘,则结果需要存储30位数字。但是您无法存储它们,因此最后15位会丢失。当与组合使用时,这尤其令人讨厌sqrt()
(例如sqrt(x*x + y*y)
:结果将仅具有7.5位数的精度。
这些是您需要注意的主要陷阱。并且一旦知道了它们,就可以尝试避免出现这种情况来制定数学公式。例如,如果您需要在循环中一次又一次地增加值,请避免这样做:
for(double f = f0; f < f1; f += df) {
经过几次迭代,较大的f
将吞噬的部分精度df
。更糟糕的是,错误将加总,导致相反的情况,较小的错误df
可能导致较差的总体结果。最好这样写:
for(int i = 0; i < (f1 - f0)/df; i++) {
double f = f0 + i*df;
因为您是将增量合并为一个乘法,所以结果f
将精确到15个十进制数字。
这仅是示例,还有其他方法可以避免由于其他原因而导致精度损失。但是,考虑所涉及的值的大小,并想象如果您要用笔和纸进行数学运算,并在每一步后取整到固定位数,将会很有帮助。
如何确保您没有问题:了解浮点算术问题,或聘请有问题的人,或使用一些常识。
第一个问题是精度。在许多语言中,您都具有“ float”和“ double”(双精度代表“ double precision”),在许多情况下,“ float”可为您提供约7位数的精度,而double则为15。常识是,如果您有在精度可能成为问题的情况下,15位数字要比7位数字好得多。在许多有轻微问题的情况下,使用“ double”意味着您会摆脱它,而“ float”则意味着您没有。假设一家公司的市值为7000亿美元。用float表示,最低位是$ 65536。用double表示,最低位约为0.012美分。因此,除非您真的非常了解自己在做什么,否则请使用double而不是float。
第二个问题更多是一个原则问题。如果您进行两个不同的计算应得出相同的结果,则通常由于舍入误差而不会这样做。应该相等的两个结果将是“几乎相等”。如果两个结果接近,则实际值可能相等。或者它们可能不是。您需要牢记这一点,并应编写和使用表示“ x绝对大于y”或“ x绝对小于y”或“ x和y可能相等”的函数。
如果使用舍入,此问题会变得更加严重,例如“将x向下舍入到最接近的整数”。如果乘以120 * 0.05,结果应为6,但是得到的是“非常接近6的某个数字”。如果然后“舍入到最接近的整数”,则“非常接近6的数字”可能会“略小于6”并四舍五入为5。请注意,精度有多大无关紧要。只要结果小于6,结果就接近 6 都没关系。
第三,有些问题很难解决。这意味着没有快速简便的规则。如果您的编译器更精确地支持“ long double”,则可以使用“ long double”,看看是否有所不同。如果没关系,那么您就可以了,或者您遇到了一个棘手的问题。如果它带来了您所期望的那种差异(例如,小数点后12位的变化),那么您可能会好起来的。如果它确实改变了您的结果,那么您有问题。请求帮忙。
当大多数人看到BigDecimal大喊一声时,大多数人都会犯错,而实际上他们只是将问题转移到其他地方。Double提供符号位:1位,指数宽度:11位。有效精度:53位(显式存储52位)。由于双重性质,整个整数越大,您失去的相对精度就越高。为了计算相对精度,我们在下面使用。
在计算中,double的相对精度使用以下公式2 ^ E <= abs(X)<2 ^(E + 1)
epsilon = 2 ^(E-10)%对于16位浮点数(半精度)
Accuracy Power | Accuracy -/+| Maximum Power | Max Interger Value
2^-1 | 0.5 | 2^51 | 2.2518E+15
2^-5 | 0.03125 | 2^47 | 1.40737E+14
2^-10 | 0.000976563 | 2^42 | 4.39805E+12
2^-15 | 3.05176E-05 | 2^37 | 1.37439E+11
2^-20 | 9.53674E-07 | 2^32 | 4294967296
2^-25 | 2.98023E-08 | 2^27 | 134217728
2^-30 | 9.31323E-10 | 2^22 | 4194304
2^-35 | 2.91038E-11 | 2^17 | 131072
2^-40 | 9.09495E-13 | 2^12 | 4096
2^-45 | 2.84217E-14 | 2^7 | 128
2^-50 | 8.88178E-16 | 2^2 | 4
换句话说,如果您希望精度为+/- 0.5(或2 ^ -1),则数字的最大大小为2 ^ 52。大于此值且浮点数之间的距离大于0.5。
如果您希望精度为+/- 0.0005(大约2 ^ -11),则数字的最大大小为2 ^ 42。大于此值且浮点数之间的距离大于0.0005。
我真的不能给出比这更好的答案。用户将需要弄清楚他们在执行必要的计算时想要的精度及其单位值(米,英尺,英寸,毫米,厘米)。在大多数情况下,float足以满足简单模拟的需要,具体取决于您要模拟的世界范围。
尽管可以这么说,但是如果您仅打算模拟一个100米乘100米的世界,那么您将在2 ^ -45附近找到一个精度等级的地方。这甚至不涉及cpu内的现代FPU如何执行超出本机类型大小的计算,只有在计算完成后,它们才会四舍五入(取决于FPU舍入模式)为本机类型大小。