Подтвердить что ты не робот

Почему поведение Ruby Float # round отличается от Python?

"Поведение функции "round" в Python" отмечает, что раунды Python плавают следующим образом:

>>> round(0.45, 1)
0.5
>>> round(1.45, 1)
1.4
>>> round(2.45, 1)
2.5
>>> round(3.45, 1)
3.5
>>> round(4.45, 1)
4.5
>>> round(5.45, 1)
5.5
>>> round(6.45, 1)
6.5
>>> round(7.45, 1)
7.5
>>> round(8.45, 1)
8.4
>>> round(9.45, 1)
9.4

Принятый ответ подтверждает, что это вызвано двояким представлением поплавок, которое является неточным, что является логичным.

Предполагая, что Ruby floats так же неточен, как и Python, почему Ruby плавает вокруг, как человек? Обманывает ли Ruby?

1.9.3p194 :009 > 0.upto(9) do |n|
1.9.3p194 :010 >     puts (n+0.45).round(1)
1.9.3p194 :011?>   end
0.5
1.5
2.5
3.5
4.5
5.5
6.5
7.5
8.5
9.5
4b9b3361

Ответ 1

Резюме

Обе реализации сталкиваются с тем же проблемами, связанными с двоичным числом с плавающей запятой.

Ruby работает непосредственно с числом с плавающей запятой с помощью простых операций (умножается на десять, настраивает и усекает).

Python преобразует двоичный номер с плавающей запятой в строку с использованием сложного алгоритма David Gay, который дает кратчайшее десятичное представление, которое точно равно двоичному числу с плавающей запятой. Это не делает никакого дополнительного округления, это точное преобразование в строку.

С кратчайшим строковым представлением в ручном режиме раунды Python до соответствующего количества десятичных знаков с использованием точных операций с строками. Цель преобразования float-to-string заключается в попытке "отменить" некоторую ошибку двоичного представления с плавающей запятой (т.е. Если вы введете 6.6, раунды Python на 6.6, а не 6.5999999999999996.

Кроме того, Ruby отличается от некоторых версий Python в режимах округления: round-away-from-zero и round-half-even.

Detail

Руби не обманывает. Он начинается с простых старых двоичных чисел с плавающей точкой, одинаковых с Python. Соответственно, он подвергается некоторым из тех же самых проблем (например, 3,35 представлены чуть более 3,35 и 4,35, которые представлены как чуть меньше 4,35):

>>> Decimal.from_float(3.35)
Decimal('3.350000000000000088817841970012523233890533447265625')
>>> Decimal.from_float(4.35)
Decimal('4.3499999999999996447286321199499070644378662109375')

Лучший способ увидеть различия в реализации - посмотреть на исходный код:

Здесь ссылка на исходный код Ruby: https://github.com/ruby/ruby/blob/trunk/numeric.c#L1587

Источник Python начинается здесь: http://hg.python.org/cpython/file/37352a3ccd54/Python/bltinmodule.c и заканчивается здесь: http://hg.python.org/cpython/file/37352a3ccd54/Objects/floatobject.c#l1080

Последний имеет обширный комментарий, который показывает различия между двумя реализациями:

Основная идея очень проста: конвертировать и округлять двойную до десятичная строка с использованием _Py_dg_dtoa, затем конвертировать эту десятичную строку вернуться к двойному с _Py_dg_strtod. Там одна незначительная сложность: Python 2.x ожидает раунда, чтобы сделать раунд-пол-от-ноль, в то время как _Py_dg_dtoa делает раунд-полу-четный. Поэтому нам нужно каким-то образом обнаружить и исправить половину случаев.

Обнаружение: среднее значение имеет вид k * 0,5 * 10 ** - ndigits для некоторое нечетное целое число k. Или, другими словами, рациональное число x точно на полпути между двумя кратными 10 ** - ndigits, если его 2-оценка точно -ndigits-1 и его 5-оценка не меньше -ndigits. Для ndigits >= 0 последнее условие выполняется автоматически для двоичного float x, так как любое такое float имеет неотрицательный 5-оценки. Для 0 > ndigits >= -22, x должен быть интегралом кратное 5 ** - ndigits; мы можем проверить это с помощью fmod. Для -22 > ndigits, нет полпути: 5 ** 23 принимает 54 бита для представления точно так, что любое нечетное кратное 0,5 * 10 ** n при n >= 23 принимает не менее 54 бит точности, чтобы точно представлять.

Исправление: простая стратегия для решения (только для промежуточных случаев) вызовите _Py_dg_dtoa с аргументом ndigits + 1 вместо ndigits (таким образом, точное преобразование в десятичный), вокруг результирующей строки вручную, а затем конвертировать назад используя _Py_dg_strtod.

Короче говоря, Python 2.7 подходит к тому, чтобы точно следовать правилу round-away-from-zero.

В Python 3.3 он имеет одинаково большую длину, чтобы точно следовать правилу round-to-even.

Здесь немного подробная информация о функции _ Py_dg_dtoa. Python вызывает функцию float для строки, потому что она реализует алгоритм, который дает кратчайшее возможное представление строк среди равных альтернатив. Например, в Python 2.6 число 1.1 отображается как 1.1000000000000001, но в Python 2.7 и более поздних версиях это просто 1.1. Дэвид Гей сложный алгоритм dtoa.c дает "результат-то, что-люди-ожидают", не теряя точности.

Этот алгоритм преобразования строк имеет тенденцию к составлению некоторых проблем, которые могут привести к любой реализации round() для двоичных чисел с плавающей запятой (т.е. меньше округления 4.35 начинается с 4.35 вместо 4.34999999999999964447286321199499070644378662109375).

Это и режим округления (round-half-even vs round-away-from-zero) являются существенными различиями между функциями Python и Ruby round().

Ответ 2

Основное различие заключается в следующем:

Python: Преобразование в десятичное, а затем круглое

Ruby:   Round и затем преобразовать в десятичный

Ruby округляет его от исходной битовой строки с плавающей запятой, но после работы с ней 10 n. Вы не можете увидеть исходное двоичное значение, не глядя очень тесно. Значения неточны, потому что они двоичные, и мы привыкли писать в десятичной форме, и, как это бывает, почти все строки с десятичной дробью, которые мы, вероятно, будем писать, не имеют точной эквивалентности в качестве базовой дробной строки.

В частности, 0.45 выглядит следующим образом:

01111111101 1100110011001100110011001100110011001100110011001101 

В шестнадцатеричном выражении 3fdccccccccccccd.

Он повторяется в двоичном формате, первая непредставленная цифра 0xc,, и умное преобразование десятичного ввода точно округлоло эту самую последнюю дробную цифру до 0xd.

Это означает, что внутри машины значение больше 0.45 примерно на 1/2 50. Это, безусловно, очень и очень небольшое число, но этого достаточно, чтобы заставить округлый алгоритм по умолчанию округлить, а не до тай-брейкера четного.

Оба Python и Ruby потенциально округляют более одного раза, так как каждая операция эффективно округляется до наименее значимого бита.

Я не уверен, что согласен с тем, что Ruby делает то, что сделал бы человек. Я думаю, что Python приближается к тому, что будет делать десятичная арифметика. Python (в зависимости от версии) применяет округло-ближайшее к десятичной строке, а Ruby применяет круглый ближайший алгоритм к вычисленному двоичному значению.

Заметим, что мы можем здесь ясно видеть, почему люди говорят, что FP неточен. Это разумно истинное утверждение, но более справедливо сказать, что мы просто не можем точно преобразовать между двоичной и самой десятичной дроби. (Некоторые из них: 0,25, 0,5, 0,75,...). Самые простые десятичные числа повторяют числа в двоичном формате, поэтому мы не можем хранить точное эквивалентное значение. Но каждое значение, которое мы можем сохранить, точно известно, и вся выполняемая на нем арифметика выполняется точно. Если бы мы написали наши дробные части в двоичном, то в первую очередь наша Арифметика FP была бы точной.

Ответ 3

Руби не обманывает. Он просто выбрал другой способ реализовать round.

В Ruby 9.45.round(1) почти эквивалентен (9.45*10.0).round / 10.0.

irb(main):001:0> printf "%.20f", 9.45
9.44999999999999928946=> nil
irb(main):002:0> printf "%.20f", 9.45*10.0
94.50000000000000000000=> nil

Итак,

irb(main):003:0> puts 9.45.round(1)
9.5

Если мы будем использовать такой способ в Python, мы также получим 9.5.

>>> round(9.45, 1)
9.4
>>> round(9.45*10)/10
9.5