Какова природа ошибки округления здесь?

Может кто-нибудь помочь мне распаковать то, что именно происходит под капотом здесь?

>>> 1e16 + 1.
1e+16
>>> 1e16 + 1.1
1.0000000000000002e+16

Я на 64-битном Python 2.7. Во-первых, я бы предположил, что, поскольку для float существует только точность 15, это просто ошибка округления. Истинным ответом с плавающей запятой может быть что-то вроде

10000000000000000.999999....

И десятичное число просто прерывается. Но второй результат заставляет меня сомневаться в этом понимании и не может быть точно представлен? Любые мысли?

[ Изменить: просто для уточнения. Я никоим образом не предлагаю, чтобы ответы были "неправильными". Ясно, что они правы, потому что, ну, они есть. Я просто пытаюсь понять, почему.]

Ответ 1

Он округляется настолько близко, насколько это возможно.

1e16 в плавающей шестнадцатеричной форме 0x4341c37937e08000.

1e16 + 2 составляет 0x4341c37937e08001.

На этом уровне величины наименьшая разница в точности, которую вы можете представить, равна 2. Добавление 1.0 точно округляет (поскольку обычно математика с плавающей точкой IEEE округляется до четного числа). Добавление значений больше 1,0 будет округлено до следующего представляемого значения.

Ответ 2

10 ^ 16 = 0x002386f26fc10000 точно представляет собой число с плавающей запятой двойной точности. Следующее представимое число равно 1e16 + 2. 1e16 + 1 правильно округляется до 1e16, а 1e16 + 1.1 правильно округляется до 1e16 + 2. Проверьте вывод этой программы на C:

#include <stdio.h>
#include <math.h>
#include <stdint.h>

int main()
{
  uint64_t i = 10000000000000000ULL;
  double a = (double)i;
  double b = nextafter(a,1.0e20); // next representable number
  printf("I=0x%016llx\n",i); // 10^16 in hex
  printf("A=%a (%.4f)\n",a,a); // double representation
  printf("B=%a (%.4f)\n",b,b); // next double
}

Вывод:

I=0x002386f26fc10000
A=0x1.1c37937e08p+53 (10000000000000000.0000)
B=0x1.1c37937e08001p+53 (10000000000000002.0000)

Ответ 3

Позвольте декодировать некоторые поплавки и посмотреть, что происходит на самом деле! Я собираюсь использовать Common Lisp, который имеет удобную функцию для получения значимости (a.k.a mantissa) и показателя числа с плавающей запятой, без необходимости перекручивать любые биты. Все используемые поплавки - это поплавки с двойной точностью IEEE.

> (integer-decode-float 1.0d0)
4503599627370496
-52
1

То есть, если мы рассмотрим значение, сохраненное в значении как целое число, это максимальная мощность 2 доступных (4503599627370496 = 2 ^ 52), уменьшенная (2 ^ -52). (Он не сохраняется как 1 с показателем 0, потому что проще, чтобы значение никогда не имело нулей слева, и это позволяет нам пропускать представление самого левого 1-го бита и иметь большую точность. Номера, не указанные в этой форме, называются denormal.)

Посмотрим на 1e16.

> (integer-decode-float 1d16)
5000000000000000
1
1

Здесь мы имеем представление (5000000000000000) * 2 ^ 1. Обратите внимание, что значение, несмотря на хорошее округлое десятичное число, не является степенью 2; это потому, что 1e16 не является силой 2. Каждый раз, когда вы умножаетесь на 10, вы умножаетесь на 2 и 5; умножение на 2 просто увеличивает показатель экспоненты, но умножение на 5 является "фактическим" умножением, и здесь мы умножились на 5 16 раз.

5000000000000000 = 10001110000110111100100110111111000001000000000000000 (base 2)

Обратите внимание, что это 53-битное двоичное число, как и должно быть, поскольку двойные поплавки имеют 53-битное значение.

Но ключ к пониманию ситуации состоит в том, что показатель степени равен 1. (Показатель, являющийся малым, является признаком того, что мы приближаемся к пределам точности.) Это означает, что значение float равно 2 ^ 1 = 2 раза это значение.

Теперь, что происходит, когда мы пытаемся представить добавление 1 к этому числу? Ну, нам нужно представить 1 в том же масштабе. Но наименьшее изменение, которое мы можем сделать в этом числе, равно 2, поскольку наименее значащий бит значения имеет значение 2!

То есть, если мы увеличиваем значение и увеличиваем минимальное возможное изменение, получим

5000000000000001 = 10001110000110111100100110111111000001000000000000001 (base 2)

и когда мы применяем показатель степени, получаем 2 * 5000000000000001 = 10000000000000002, что является точно значением, которое вы наблюдали. Вы можете использовать только 10000000000000000 или 10000000000000002, а 10000000000000001.1 ближе к последнему.

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

Ответ 4

С помощью numpy вы можете увидеть следующее большее и меньшее число отображаемых чисел с плавающей запятой IEEE:

>>> import numpy as np
>>> huge=1e100
>>> tiny=1e-100
>>> np.nextafter(1e16,huge)
10000000000000002.0
>>> np.nextafter(1e16,tiny)
9999999999999998.0

Итак:

>>> (np.nextafter(1e16,huge)-np.nextafter(1e16,tiny))/2.0
2.0

И:

>>> 1.1>2.0/2
True

Следовательно, 1e16 + 1.1 правильно округляется до следующего большего числа IEEE, представляющего 10000000000000002.0

Как есть:

>>> 1e16+1.0000000000000005
1.0000000000000002e+16

и 1e16- (что-то немного больше 1) округляется на 2 до следующего меньшего номера IEEE:

>>> 1e16-1.0000000000000005
9999999999999998.0

Имейте в виду, что 32-разрядный и 64-разрядный Python не имеет значения. Это размер IEEE format, который имеет значение. Также имейте в виду, что чем больше значение числа, тем выше значение epsilon (разброс между двумя следующими большими и меньшими значениями IEEE) .

Вы также можете увидеть это в битах:

>>> def f_to_bits(f): return struct.unpack('<Q', struct.pack('<d', f))[0]
... 
>>> def bits_to_f(bits): return struct.unpack('<d', struct.pack('<Q', bits))[0]
... 
>>> bits_to_f(f_to_bits(1e16)+1)
1.0000000000000002e+16
>>> bits_to_f(f_to_bits(1e16)-1)
9999999999999998.0