Существуют ли два числа, которые умножают (или делят) друг друга, вносят ошибку? - PullRequest
0 голосов
/ 21 февраля 2019

Вот банк тестов, которые я делаю, и узнаю, как FP basic ops (+, -, *, /) могут привести к ошибкам:

#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
}

Можете ли вы показать мне два значения (original и a), которые, однажды * (или /), из-за математики FP, привносят ошибку?

А если они существуют, можно ли установить, была ли эта ошибка введена * или /?И как?(поскольку вам нужно и то, и другое для возврата к значению; 80 bit?)

С + легко (просто добавьте 0.499999999999999944488848768742172978818416595458984375 к 0.5, и вы получите 1.0, как для 0.5 + 0.5).

Но я не могу сделать то же самое с * или /.

Ответы [ 3 ]

0 голосов
/ 21 февраля 2019

Умножьте любые два больших числа, и, скорее всего, произойдет ошибка, поскольку представимые значения имеют большие расстояния в высоком диапазоне значений.

Хотя эта ошибка может быть значительной вВ абсолютном выражении она все еще мала по отношению к размеру самого числа, поэтому при выполнении обратного деления ошибка первой операции уменьшается в том же соотношении и полностью исчезает.Таким образом, эта последовательность операций является стабильной.

Если результат умножения будет больше, чем максимальное представляемое значение, то он переполнится до бесконечности (может зависеть от конфигурации), и в этом случае выиграет обратное делениене приводят к исходному значению, но остаются в бесконечности.

Аналогично, если вы делите большое число, вы потенциально потеряете минимальное представимое значение, что приведет к нулю или ненормальному значению.

Числа не обязательно должны быть огромными.Просто легче понять проблему, рассматривая огромные ценности.Проблема относится и к довольно маленьким значениям.Например:

2.100000000000000088817841970012523233890533447265625 ×
2.100000000000000088817841970012523233890533447265625

Правильный результат:

4.410000000000000373034936274052605470949292688633679117285...

Пример результата с плавающей запятой:

4.410000000000000142108547152020037174224853515625

Ошибка:

2.30926389122032568296724439173008679117285652827862296732064351090230047702789306640625
× 10^-16
0 голосов
/ 22 февраля 2019

Существуют ли два числа, которые умножают (или делят) друг друга на ошибку?

Это гораздо легче увидеть с "%a".

Когда точность результата недостаточна, происходит округление.Обычно double имеет 53 бита двоичной точности.Умножение 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
0 голосов
/ 21 февраля 2019

Вывод:

#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.

при условии, что базовая 64-битная двоичная плавающая точка IEEE-754 с правильным округлением до ближайшего, связана с четным.

Очевидно, что 999999999999999983222784 отличается от точного математического результата 1000000000000 • 1000000000000, 1000000000000000000000000.

...