是否存在两个相乘(或相除)的数字会引入错误?

时间:2019-02-21 14:12:13

标签: c++ floating-point floating-accuracy

这是我正在进行的bank测试,了解FP基本操作(+-*/ )会引入错误:

#include <iostream>
#include <math.h>

int main() {
    std::cout.precision(100);

    double a = 0.499999999999999944488848768742172978818416595458984375;

    double original = 47.9;
    double target = original * a;    
    double back = target / a;

    std::cout <<  original << std::endl;
    std::cout <<  back << std::endl;
    std::cout <<  fabs(original - back) << std::endl; // its always 0.0 for the test I did
}

您能告诉我两个值(originala),一旦*(或/),由于FP数学而引入错误?

并且如果存在,是否可以确定该错误是由*还是/引起的?如何? (因为同时需要两个值才能返回值; 80 bit?)

使用+很容易(只需将0.499999999999999944488848768742172978818416595458984375添加到0.5,就可以得到1.0,就像0.5 + 0.5一样。)

但是我无法使用*/做同样的事情。

3 个答案:

答案 0 :(得分:0)

输出:

#include <cstdio>

int main(void)
{
    double a = 1000000000000.;
    double b = 1000000000000.;
    std::printf("a = %.99g.\n", a);
    std::printf("a = %.99g.\n", b);
    std::printf("a*b = %.99g.\n", a*b);
}

是:

a = 1000000000000.
a = 1000000000000.
a*b = 999999999999999983222784.

假定IEEE-754基本的64位二进制浮点具有正确的舍入至最接近的值,并与偶数相关。

很明显,999999999999999983222784与精确的数学结果1000000000000•1000000000000,1000000000000000000000000不同。

答案 1 :(得分:0)

将任意两个大数相乘,由于可表示的值在较大的值范围内相距很远,因此可能会出现错误。

虽然此错误从绝对值上来说可能很大,但相对于数字本身的大小而言仍然很小,因此,如果执行反向除法,则第一个操作的错误将以相同的比例缩小,并且完全消失。因此,此操作顺序是稳定的。

如果相乘的结果大于可表示的最大值,则它将溢出到无穷大(可能取决于配置),在这种情况下,反向除法不会导致原始值,而是保持无穷大。

类似地,如果用大数除,则可能会下溢最小的可表示值,从而导致零或次正规值。

数字不一定必须很大。在考虑巨大价值时,更容易理解这个问题。这个问题也适用于很小的值。例如:

2.100000000000000088817841970012523233890533447265625 ×
2.100000000000000088817841970012523233890533447265625

正确的结果:

4.410000000000000373034936274052605470949292688633679117285...

浮点结果示例:

4.410000000000000142108547152020037174224853515625

错误:

2.30926389122032568296724439173008679117285652827862296732064351090230047702789306640625
× 10^-16

答案 2 :(得分:0)

  

是否存在两个彼此相乘(或相除)的数字会引入错误?

使用"%a"可以更容易地看到它。

结果的精度不足时,将进行四舍五入。通常double具有53位的 binary 精度。将下面的2个27位数字相乘得到一个确切的53位答案,但是2个28位数字不能形成55位有效答案。

除法很容易演示,只需尝试1.0/n*n

int main(void) {
  double a = 1 + 1.0/pow(2,26);
  printf("%.15a,  %.17e\n", a, a);
  printf("%.15a,  %.17e\n", a*a, a*a);
  double b = 1 + 1.0/pow(2,27);
  printf("%.15a,  %.17e\n", b, b);
  printf("%.15a,  %.17e\n", b*b, b*b);

  for (int n = 47; n < 52; n += 2) {
    volatile double frac = 1.0/n;
    printf("%.15a,  %.17e %d\n", frac, frac, n);
    printf("%.15a,  %.17e\n", frac*n, frac*n);
  }
  return 0;
}

输出

//v-------v         27 significant bits.
0x1.000000400000000p+0,  1.00000001490116119e+00
//v-------------v   53 significant bits.
0x1.000000800000100p+0,  1.00000002980232261e+00
//v-------v         28 significant bits.
0x1.000000200000000p+0,  1.00000000745058060e+00
//v--------------v  not 55 significant bits.
0x1.000000400000000p+0,  1.00000001490116119e+00
//              ^^^ all zeros here, not the expected mathematical answer.

0x1.5c9882b93105700p-6,  2.12765957446808505e-02 47
0x1.000000000000000p+0,  1.00000000000000000e+00
0x1.4e5e0a72f053900p-6,  2.04081632653061208e-02 49
0x1.fffffffffffff00p-1,  9.99999999999999889e-01  <==== Not 1.0
0x1.414141414141400p-6,  1.96078431372549017e-02 51
0x1.000000000000000p+0,  1.00000000000000000e+00