浮点舍入误差的解决方案


18

在构建处理大量数学计算的应用程序时,我遇到了某些数字会导致舍入错误的问题。

虽然我知道浮点数不是精确的,但问题是我如何处理确切的数字,以确保在对它们进行计算时,浮点数舍入不会引起任何问题?


2
您面临一个特定的问题吗?有很多测试方法,可以解决一些问题。可以有多个答案的问题不适合问答形式。最好以能够给出正确答案的方式来定义您所遇到的问题,而不是为想法和建议投下网。

我正在构建具有许多数学计算能力的软件应用程序。我知道NUNIT或JUNIT测试会很好,但是很想对如何使用“数学计算”解决问题有个想法。
JNL

1
您能否举一个要测试的计算示例?通常不会对原始数学进行单元测试(除非您正在测试自己的数值类型),而是要测试类似的东西distanceTraveled(startVel, duration, acceleration)

一个例子是处理小数点。例如,假设我们正在建立一堵墙,对dist x-0到x = 14.589进行特殊设置,然后从x = 14.589到x =墙的末端进行一些设置。转换成二进制文件时的距离.589不一样。...特别是如果我们加上一些距离...例如14.589 + 0.25将不等于二进制文件中的14.84。...我希望它不会引起混淆?
JNL

1
@MichaelT感谢您编辑问题。帮助很大。由于这是新事物,因此在如何构架问题上不太好。:) ...但是很快会好起来的。
JNL 2013年

Answers:


22

创建可替代浮点数舍入的数字类型的三种基本方法。这些的共同主题是它们以各种方式使用整数数学。

合理性

用分子和分母表示作为一个整体的数字和有理数。该数字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存储为252。这可以处理任意精度。我相信这是Java BigDecimal的内部用法(最近没有查看过)的用途。在某个时候,您将需要使它退回到这种格式并显示出来-这可能涉及四舍五入(同样,您可以控制它的位置)。


确定表示形式的选择后,您可以找到使用该表示形式的现有第三方库,也可以编写自己的库。编写自己的代码时,请确保对其进行单元测试,并确保正确进行数学运算。


2
这是一个很好的开始,但当然不能完全解决舍入问题。π,e和√2 等无理数没有严格的数字表示;如果需要精确的表示,则需要象征性地表示它们;如果只想最小化舍入误差,则需要对它们进行尽可能晚的评估。
Caleb 2013年

@Caleb对于非理性因素,需要对它们进行评估,以至于四舍五入都可能导致问题的地方。例如,22/7精确到pi的0.1%,355/113精确到10 ^ -8。如果仅使用小数点后3位的数字,则具有3.141592653的位置应避免在小数点后3位舍入错误。

@MichaelT:对于有理数的添加,您不需要查找LCM,并且不这样做会更快(并且取消之后的“ LSB零”更快,并且只有在绝对必要时才完全简化)。通常,对于有理数,通常仅是“分子/分母”,或者是“分子/分母<<指数”(而不是“整数+分子/分母”)。同样,您的“浮动固定点”是浮点表示形式,最好将其描述为“任意大小的浮动点”(以区别于“固定大小的浮点”)。
布伦丹

您的某些术语有些含糊-浮动定点没有意义-我想您是想说浮动十进制。
jk。

10

如果浮点值存在舍入问题,而您又不想遇到舍入问题,那么从逻辑上讲,唯一的做法是不使用浮点值。

现在的问题变成:“我该如何进行不包含浮点变量的非整数值的数学运算?” 答案是任意精度的数据类型。计算较慢,因为它们必须用软件而不是硬件来实现,但它们是准确的。您没有说使用什么语言,所以我不推荐一个软件包,但是对于大多数流行的编程语言都有任意的精度库。


我现在正在使用VC ++。但是,我也希望获得有关其他编程语言的更多信息。
JNL 2013年

即使没有浮点值,您仍然会遇到全面问题。
乍得

2
@Chad True,但目标并不是消除舍入问题(该问题将一直存在,因为在您使用的任何基数中,有些数字没有确切的表示形式,并且您没有无限的内存和处理能力),而是将其减少到对您尝试的计算没有影响的程度。
Iker,2016年

@Iker你是对的。尽管您,也不是询问问题的人都指定了他们要实现的精确计算以及所需的精度。他需要先回答这个问题,然后再将枪支引入数字理论。只是说lot of mathematical calculations没有帮助,也没有给出答案。在绝大多数情况下(如果您不使用货币),那么浮动就足够了。
乍得

@Chad这是一个公平的观点,OP肯定没有足够的数据来告诉他们所需的精确度到底是多少。
Iker,2016年

7

浮点算术通常非常精确(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个十进制数字。

这仅是示例,还有其他方法可以避免由于其他原因而导致精度损失。但是,考虑所涉及的值的大小,并想象如果您要用笔和纸进行数学运算,并在每一步后取整到固定位数,将会很有帮助。


2

如何确保您没有问题:了解浮点算术问题,或聘请有问题的人,或使用一些常识。

第一个问题是精度。在许多语言中,您都具有“ 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位的变化),那么您可能会好起来的。如果它确实改变了您的结果,那么您有问题。请求帮忙。


1
浮点数学没有“常识”。
whatsisname

了解更多信息。
gnasher729

0

当大多数人看到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舍入模式)为本机类型大小。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.