Почему ** 2! = A * a для некоторых плавающих?
$ python --version
Python 2.7.15
$ type test.py
import random
while True:
a = random.uniform(0, 1)
b = a ** 2
c = a * a
if b != c:
print "a = {}".format(a)
print "a ** 2 = {}".format(b)
print "a * a = {}".format(c)
break
$ python test.py
a = 0.145376687586
a ** 2 = 0.0211343812936
a * a = 0.0211343812936
Я смог воспроизвести это только в Windows-сборке Python - точнее: Python 2.7.15 (v2.7.15:ca079a3ea3, Apr 30 2018, 16:30:26) [MSC v.1500 64 bit (AMD64)] on win32
. На моей установке в Arch Linux Python (Python 2.7.15 (default, May 1 2018, 20:16:04) [GCC 7.3.1 20180406] on linux2
) цикл, кажется, не заканчивается, указывая, что a**2 = a * a
инвариант.
Что здесь происходит? Я знаю, что поплавки IEEE приходят с множеством заблуждений и особенностей (это, например, не отвечает на мой вопрос), но я не вижу, какая часть спецификации или какая реализация **
могла бы это позволить.
Чтобы адресовать дублирующее пометку: скорее всего, это не проблема математической задачи с плавающей запятой IEEE и проблема с реализацией оператора **
. Следовательно, это не дубликат вопросов, которые задают только вопросы с плавающей точкой, такие как точность или ассоциативность.
Ответы
Ответ 1
Python полагается на базовую платформу для своей арифметики с плавающей запятой. Я предполагаю, что оператор Pythons **
использует реализацию pow
(как используется в C) (подтвержденный пользователем2357112 со ссылкой на исходный код Python 2.7.15).
Как правило, pow
реализуется с помощью (приближений) логарифмов и экспонент, частично. Это необходимо, так как pow
поддерживает нецелые аргументы. (Конечно, эта общая реализация не исключает специализации для подмножеств своей области.)
По- pow
внедрение Microsofts не очень хорошо. Следовательно, для pow(a, 2)
он может возвращать результат, не равный a*a
.
Ответ 2
a ** 2
использует функцию мощности с плавающей запятой (например, ту, которую вы можете найти в стандартном Cmath lib), которая может поднять любое число до любой мощности.
a * a
просто умножается один раз, он более подходит для этого случая и не подвержен ошибкам точности (что еще более верно для целых чисел), например a ** 2
.
Для с плавающей запятой a
, если вы хотите поднять до мощности, скажем, 5, используя
a * a * a * a * a
вам будет лучше с a**5
потому что повторное умножение теперь подвержено ошибкам накопления с плавающей запятой, и это намного медленнее.
a ** b
интереснее, когда b
велико, потому что он более эффективен, например. Но точность может отличаться, поскольку он использует алгоритм с плавающей запятой.