Почему ** 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 велико, потому что он более эффективен, например. Но точность может отличаться, поскольку он использует алгоритм с плавающей запятой.