У меня есть часть кода, который ведет себя по-другому в зависимости от того, прохожу ли я словарь для получения коэффициентов преобразования или использую ли я их непосредственно.
Следующая часть кода распечатает 1.0 == 1.0 -> False
Но если Вы заменяете factors[units_from]
с 10.0
и factors[units_to ]
с 1.0 / 2.54
это распечатает 1.0 == 1.0 -> True
#!/usr/bin/env python
base = 'cm'
factors = {
'cm' : 1.0,
'mm' : 10.0,
'm' : 0.01,
'km' : 1.0e-5,
'in' : 1.0 / 2.54,
'ft' : 1.0 / 2.54 / 12.0,
'yd' : 1.0 / 2.54 / 12.0 / 3.0,
'mile' : 1.0 / 2.54 / 12.0 / 5280,
'lightyear' : 1.0 / 2.54 / 12.0 / 5280 / 5.87849981e12,
}
# convert 25.4 mm to inches
val = 25.4
units_from = 'mm'
units_to = 'in'
base_value = val / factors[units_from]
ret = base_value * factors[units_to ]
print ret, '==', 1.0, '->', ret == 1.0
Позвольте мне сначала сказать, что я вполне уверен, что продолжается здесь. Я видел его прежде в C, просто никогда в Python, но начиная с Python в реализованном в C, мы видим его.
Я знаю, что числа с плавающей точкой изменят значения, собирающиеся от регистра ЦП кэшироваться и отступать. Я знаю, что сравнение, что должно быть двумя равными переменными, возвратит false, если один из них был разбит на страницы, в то время как другой остался резидентный объект в регистре.
Вопросы
Примечание стороны
Это - очевидно, часть разделенного вниз пример, но что я пытаюсь сделать, идется с классами длины, объема, и т.д. который может выдержать сравнение с другими объектами того же класса, но с различными единицами.
Риторические вопросы
Спасибо за ответы. Большинство из них были очень хорошими и содержали хорошие ссылки, поэтому я просто скажу это и отвечу на свой вопрос.
Каспин опубликовал эту ссылку .
Он также упомянул, что Google Tests использовал сравнение ULP, и когда я взглянул на код Google, я увидел, что они упоминают ту же самую ссылку на cygnus-software.
Я завершил реализацию некоторых алгоритмов на C как расширение Python, а затем обнаружил, что могу сделать это и на чистом Python. Код размещен ниже.
В конце концов, я, вероятно, просто добавлю различия ULP в свой набор уловок.
Было интересно посмотреть, сколько чисел с плавающей запятой находится между двумя равными числами, которые никогда не покидают память. В одной из статей или в коде Google, который я прочитал, говорилось, что 4 - хорошее число ... но здесь я смог набрать 10.
>>> f1 = 25.4
>>> f2 = f1
>>>
>>> for i in xrange(1, 11):
... f2 /= 10.0 # to cm
... f2 *= (1.0 / 2.54) # to in
... f2 *= 25.4 # back to mm
... print 'after %2d loops there are %2d doubles between them' % (i, dulpdiff(f1, f2))
...
after 1 loops there are 1 doubles between them
after 2 loops there are 2 doubles between them
after 3 loops there are 3 doubles between them
after 4 loops there are 4 doubles between them
after 5 loops there are 6 doubles between them
after 6 loops there are 7 doubles between them
after 7 loops there are 8 doubles between them
after 8 loops there are 10 doubles between them
after 9 loops there are 10 doubles between them
after 10 loops there are 10 doubles between them
Также интересно, сколько чисел с плавающей запятой находится между равными числами, когда одно из них записано. вывести в виде строки и прочитать обратно.
>>> # 0 degrees Fahrenheit is -32 / 1.8 degrees Celsius
... f = -32 / 1.8
>>> s = str(f)
>>> s
'-17.7777777778'
>>> # floats between them...
... fulpdiff(f, float(s))
0
>>> # doubles between them...
... dulpdiff(f, float(s))
6255L
import struct
from functools import partial
# (c) 2010 Eric L. Frederich
#
# Python implementation of algorithms detailed here...
# from http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
def c_mem_cast(x, f=None, t=None):
'''
do a c-style memory cast
In Python...
x = 12.34
y = c_mem_cast(x, 'd', 'l')
... should be equivilent to the following in c...
double x = 12.34;
long y = *(long*)&x;
'''
return struct.unpack(t, struct.pack(f, x))[0]
dbl_to_lng = partial(c_mem_cast, f='d', t='l')
lng_to_dbl = partial(c_mem_cast, f='l', t='d')
flt_to_int = partial(c_mem_cast, f='f', t='i')
int_to_flt = partial(c_mem_cast, f='i', t='f')
def ulp_diff_maker(converter, negative_zero):
'''
Getting the ulp difference of floats and doubles is similar.
Only difference if the offset and converter.
'''
def the_diff(a, b):
# Make a integer lexicographically ordered as a twos-complement int
ai = converter(a)
if ai < 0:
ai = negative_zero - ai
# Make b integer lexicographically ordered as a twos-complement int
bi = converter(b)
if bi < 0:
bi = negative_zero - bi
return abs(ai - bi)
return the_diff
# double ULP difference
dulpdiff = ulp_diff_maker(dbl_to_lng, 0x8000000000000000)
# float ULP difference
fulpdiff = ulp_diff_maker(flt_to_int, 0x80000000 )
# default to double ULP difference
ulpdiff = dulpdiff
ulpdiff.__doc__ = '''
Get the number of doubles between two doubles.
'''
Позвольте мне сначала ответить, сказав, что вам следует прочитать классику Дэвида Голдберга Что должен знать каждый компьютерный ученый об арифметике с плавающей запятой .
Как уже говорили некоторые другие комментаторы, замеченное вами несоответствие по своей сути связано с моделью с плавающей запятой и не имеет ничего общего с регистрами, кешем или памятью.
Согласно модели с плавающей запятой, 2.54 на самом деле представляется как
>>> 2859785763380265 * 2 ** -50
2.54
Это представление, однако, не является точным:
>>> from fractions import Fraction
>>> float(Fraction(2859785763380265, 2 ** 50) - Fraction(254, 100))
3.552713678800501e-17
Теперь, выражение, которое вы оцениваете на самом деле:
>>> 25.4 / 10 * (1/2.54)
0.99999999999999989
Проблема заключается в 1 / 2.54:
>>> Fraction.from_float(1/2.54)
Fraction(1773070719437203, 4503599627370496)
Но что вы ожидаете, так это
>>> 1/Fraction.from_float(2.54)
Fraction(1125899906842624, 2859785763380265)
Чтобы ответить на ваши вопросы:
Как лучше всего избежать проблем вот так? ... На Python или вообще.
Какая проблема? Вы работаете с физическими измерениями. Если у вас нет действительно сложного оборудования, ошибка в ваших измерениях будет на несколько порядков выше, чем эпсилон с плавающей запятой. Так зачем писать код, который зависит от чисел, которые должны быть точными до 16 значащих цифр?
Должны ли компиляторы поддерживать возможность заменить все проверки равенства с плавающей запятой на функция «достаточно близко»?
Если бы это было так, вы бы получили странные результаты:
>>> float.tolerance = 1e-8 # hypothetical "close enough" definition
>>> a = 1.23456789
>>> b = 1.23456790
>>> c = 1.23456791
>>> a == b
True
>>> b == c
True
>>> a == c
False
Если вы думаете, что сейчас достаточно сложно хранить числа с плавающей запятой в словаре, попробуйте использовать нетранзитивную функцию = =
оператор! И производительность была бы отстой, потому что единственный способ гарантировать x == y
→ hash (x) == hash (y)
- это иметь одинаковый хэш-код для всех чисел с плавающей запятой. И это было бы несовместимо с целыми числами.
Чтобы сравнить числа с плавающей запятой в целом, сравните абсолютное значение разности чисел с плавающей запятой с выбранной дельтой, которая достаточно мала, чтобы соответствовать вашим потребностям.
Риторические вопросы
Я полагаю, что для python это то же самое, что и дельта, используемая для сравнение может отличаться, выбор должен быть на усмотрение разработчика. Это означает, что невозможно обеспечить полностью автоматическое преобразование по умолчанию.
Разница в том, что если вы замените факторов [units_to]
на 1.0 / 2.54
, вы сделаете:
(base_value * 1.0) / 2.54
Со словарем, вы делаете:
base_value * (1.0 / 2.54)
Порядок округления имеет значение. Это легче увидеть, если вы это сделаете:
>>> print (((25.4 / 10.0) * 1.0) / 2.54).__repr__()
1.0
>>> print ((25.4 / 10.0) * (1.0 / 2.54)).__repr__()
0.99999999999999989
Обратите внимание, что здесь нет недетерминированного или неопределенного поведения. Существует стандарт IEEE-754, которому реализации должны соответствовать (чтобы не утверждать, что они всегда соблюдают ).
Я не думаю, что должна быть автоматическая достаточно близкая замена. Часто это эффективный способ решения проблемы, но решать, использовать ли его и как это делать, должен сам программист.
Наконец, есть, конечно, варианты арифметики произвольной точности, включая python-gmp и decimal . Подумайте, действительно ли вам нужны они, потому что они действительно сильно влияют на производительность.
Нет проблем с перемещением между обычными регистрами и кешем. Возможно, вы думаете о 80-битной расширенной точности x86.
Как было показано, сравнение двух плавающих чисел (или двойных и т.д.) может быть проблематичным. Как правило, вместо сравнения на точное равенство их следует проверять на границу погрешности. Если они находятся в пределах границы ошибки, то они считаются равными.
Это гораздо легче сказать, чем сделать. Природа плавающей точки делает фиксированную границу ошибки бесполезной. Небольшая граница ошибки (например, 2*float_epsilon) хорошо работает, когда значения близки к 0.0, но потерпит неудачу, если значения близки к 1000. Граница ошибки для значений размером 1 000 000.0 будет слишком слабой для значений, близких к 0.0.
Лучшее решение - знать область математики и выбирать подходящую границу погрешности в каждом конкретном случае.
Когда это непрактично или вы ленивы, Единицы в последнем месте (ULPs) - очень новое и надежное решение. Все детали довольно сложны, вы можете прочитать больше здесь.
Основная идея заключается в следующем: число с плавающей запятой состоит из двух частей, мантиссы и экспоненты. Обычно ошибки округления изменяют мантиссу лишь на несколько шагов. Когда значение близко к 0.0, эти шагов точно равны float_epsilon. Когда значение с плавающей точкой приближается к 1 000 000, шаги будут почти равны 1.
Тест Google использует ULP для сравнения чисел с плавающей точкой. Они выбрали значение по умолчанию 4 ULP для того, чтобы два числа с плавающей точкой сравнивались одинаково. Вы также можете использовать их код в качестве ссылки для создания собственного компаратора с плавающей точкой в стиле ULP.