Какова природа ошибки округления здесь?
Может кто-нибудь помочь мне распаковать то, что именно происходит под капотом здесь?
>>> 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