Вопрос по python, numerical – Арифметика Python с небольшими числами
Я получаю следующий неожиданный результат, когда делаю арифметику с небольшими числами в Python:
<code>>>> sys.float_info sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1) >>> (1. - (1.e-17) ) < 1. False </code>
Я знаю, что числа с плавающей запятой не имеют бесконечной точности, но они должны уметь обрабатывать «большие» маленькие числа, такие как 1e-17, не так ли?
>>> import numpy as np
>>> np.nextafter(1., 0.)
0.99999999999999989
1.
, в направлении0.
Похоже1. - 1.e-17
просто ближе к1.
чемnumpy.nextafter(1., 0.)
, так когда1. - 1.e-17
оценивается, это дает вам 1 обратно. Не имеет смысла использовать какой-то другой поплавок, который был еще дальше.
Связанный вопрос -> Увеличивает значение с плавающей запятой Python на наименьшее возможное количество
epsilon
действительно в возвращаемом значенииsys.float_info
.
Эпсилон (или) наименьшее число такое, что 0.5 + ≠ 0.5 И 0.5 - ≠ 0.5
Python говорит вам, что наименьшее число, которое приведет к0.5
увеличивать или уменьшать повторно - этоepsilon=2.220446049250313e-16
- но это только для значения 0,5. Вы пытаетесь увеличить1.0
по1.0e-17
. Это большее значение (1,0 против 0,5), которое увеличивается на меньшее число, чем для 0,5 (1,0e-17 против 2,2e-16). Вы примерно на порядок отклонены, поскольку значение приращения 1.0e-17 на порядок меньше относительного эпсилона для 1.0.
Вы можете увидеть это здесь:
Они меняют значение0.5
>>> 0.5+sys.float_info.epsilon
0.5000000000000002
>>> 0.5-sys.float_info.epsilon
0.4999999999999998
Эти значения не:
>>> 0.5+sys.float_info.epsilon/10.0
0.5
>>> 0.5-sys.float_info.epsilon/10.0
0.5
>>> 5.0+sys.float_info.epsilon
5.0
>>> 5.0-sys.float_info.epsilon
5.0
Explanation:
IEEE 754 определяет формат с плавающей запятой, используемый сегодня на большинстве стандартных компьютеров (специализированные компьютеры или библиотеки могут использовать другой формат 64 битный формат IEEE 754 использует 53 бита точности для вычисления и 52 для сохранения в мантиссе значения с плавающей запятой. Поскольку у вас есть фиксированные 52/53 бит для работы, величина и точность мантиссы изменяется для больших / меньших значений. Таким образом, затем изменяется как относительная величина числа с плавающей точкой. Значение для 0,5 отличается от значения для 1,0 и для 100,0.
По ряду очень веских и специфичных для платформы причин (хранение и представление, округление и т. Д.), Даже если вымо используйте меньшее число, epsilon определяется как 52-битная точность для 64-битного формата с плавающей запятой. Поскольку в большинстве реализаций Python для поплавка используется двойной плавающий Си, это можно продемонстрировать:
>>> 2**-52==sys.float_info.epsilon
True
Смотри сколько битва платформа подойдет:
>>> 0.5 + 2.0**-53
0.5000000000000001
>>> 0.5 - 2.0**-53
0.4999999999999999
>>> 0.5 + 2.0**-54
0.5 # fail for 0.5 + 54 bits...
>>> 0.5 - 2.0**-54
0.49999999999999994 # OK for minus
>>> 0.5 - 2.0**-55
0.5 # fail for 0.5 minus 55 bits...
Существует несколько способов решения вашей проблемы:
Вы можете использовать концепцию C99 Nextafter для расчета значения соответствующего эпсилона. Для Python используйте либо numpy, либо класс Decimal для вычисленияnextafter
. Ещеnextafter
в моем предыдущем ответеВО Используйте целые числа. 64-разрядное целое число будет четко обрабатывать значение эпсилона в 17-м порядке без округления. Используйте математическую библиотеку произвольной точности.Десятичны входит в стандартный дистрибутив Python.Важной концепцией является то, что значение относительно значения (и если вы увеличиваете или уменьшаете).
Это можно увидеть здесь:
>>> numpy.nextafter(0.0,1.0)-0.0
4.9406564584124654e-324 # a relative epsilon value of 4.94e-324
>>> numpy.nextafter(0.01,1.0)-0.01
1.7347234759768071e-18 # 1e-17 would still work...
>>> numpy.nextafter(0.1,1.0)-0.1
1.3877787807814457e-17 # 1e-17 would >>barely<< work...
>>> numpy.nextafter(0.5,1.0)-0.5
1.1102230246251565e-16 # a relative epsilon value of 1.1e-16
>>> numpy.nextafter(500.0,501.0)-500.0
5.6843418860808015e-14 # relative epsilon of 5.6e-14
>>> numpy.nextafter(1e17,1e18)-1e17
16.0 # the other end of the spectrum...
Так что вы можете видеть, что 1e-17 будет легко работать, чтобы увеличивать значения между 0,0 и 0,1, но не намного больше значений, чем это. Как вы можете видеть выше, относительное значение 1e17 равно 16.
math.ldexp(x,i)
такой же какx * (2**i)
ваши примеры будут понятнее с2.0**-52
противmath.ldexp(1.0, -52)
Просто говорю'..
большие" маленькие числа, такие как 1e-17, не так ли?
Не обязательно (это зависит от чисел). Afloat
не может точно представлять либо1e-17
или1-(1e-17)
. В случае последнего, ближайшее число, которое этоможе представляет собой1
.
Я предлагаю тебе прочитать Что каждый ученый должен знать об арифметике с плавающей точкой.
Если вам нужен такой уровень точности, рассмотрите Десятичный модуль
>>> decimal.Decimal(1.0)-decimal.Decimal('1.0e-17')
Decimal('0.999999999999999990')
>>> decimal.Decimal(1.0)-decimal.Decimal('1.0e-17')<decimal.Decimal(1.0)
True
А также
>>> decimal.Decimal(1.0)-decimal.Decimal('1.0e-17')<1.0
True
Осторожно с последним, потому что вы можете получить ошибки преобразования.
Другие предложили Что каждый учёный-компьютерщик должен знать об арифметике с плавающей точкой. и я тоже рекомендую Не храните это на поплавке
>>> from decimal import Decimal
>>> Decimal('1')-Decimal(str(10**-17)) < Decimal('1')
True
десятичный модуль, для такой точности!