Почему fmod (1.0,0.1) == .1? - PullRequest
       15

Почему fmod (1.0,0.1) == .1?

4 голосов
/ 18 ноября 2010

Я впервые испытал это явление в Python, но оказалось, что это общий ответ, например, в MS Excel.Вольфрам Альфа дает интересный шизоидный ответ, где говорится, что рациональное приближение нуля равно 1/5.( 1.0 мод 0.1 )

С другой стороны, если я реализую определение вручную, это даст мне «правильный» ответ (0).Что здесь происходит.Я что-то пропустил?

Ответы [ 6 ]

23 голосов
/ 18 ноября 2010

Потому что 0.1 не 0,1; это значение не может быть представлено в двойной точности, поэтому оно округляется до ближайшего числа двойной точности, а именно:

0.1000000000000000055511151231257827021181583404541015625

Когда вы звоните fmod, вы получаете остаток от деления на значение, указанное выше, а именно:

0.0999999999999999500399638918679556809365749359130859375

округляется до 0.1 (или, может быть, 0.09999999999999995) при печати. ​​

Другими словами, fmod работает отлично, но вы не даете ему тот вклад, который вы считаете.


Редактировать: Ваша собственная реализация дает вам правильный ответ, потому что он менее точен , хотите верьте, хотите нет. Прежде всего, обратите внимание, что fmod вычисляет остаток без ошибок округления; единственный источник неточности - ошибка представления, введенная с использованием значения 0.1. Теперь давайте пройдемся по вашей реализации и посмотрим, как возникшая ошибка округления в точности устраняет ошибку представления.

Оцените a - floor(a/n) * n один шаг за раз, отслеживая точные значения, вычисленные на каждом этапе:

Сначала мы оцениваем 1.0/n, где n является ближайшим приближением двойной точности к 0.1, как показано выше. Результат этого деления примерно:

9.999999999999999444888487687421760603063276150363492645647081359...

Обратите внимание, что это значение не является представимым числом двойной точности - поэтому оно получает округлено . Чтобы увидеть, как происходит это округление, давайте посмотрим на число в двоичном, а не в десятичном виде:

1001.1111111111111111111111111111111111111111111111111 10110000000...

Пробел указывает, где происходит округление до двойной точности. Так как часть после точки округления больше, чем точная половина пути, это значение округляется до точно 10.

floor(10.0), как и ожидалось, 10.0. Так что все, что осталось, это вычислить 1.0 - 10.0*0.1.

В двоичном формате точное значение 10.0 * 0.1 равно:

1.0000000000000000000000000000000000000000000000000000 0100

опять же, это значение не может быть представлено как двойное, и поэтому округляется в позиции, указанной пробелом. На этот раз он округляется до 1.0, поэтому окончательное вычисление равно 1.0 - 1.0, что, конечно, 0.0.

Ваша реализация содержит две ошибки округления, которые в данном случае точно устраняют ошибку представления значения 0.1. fmod, напротив, является всегда точным (по крайней мере, на платформах с хорошей библиотекой чисел) и предоставляет ошибку представления 0.1.

1 голос
/ 18 ноября 2010

С man fmod:

Функция fmod () вычисляет остаток с плавающей запятой от деления x на y.Возвращаемое значение x - n * y, где n - это отношение x / y, округленное до нуля до целого числа.

Итак, что происходит:

  1. В fmod(1.0, 0.1) 0,1 фактически немного больше 0,1, потому что значение не может быть точно представлено как число с плавающей точкой.
  2. Так что n = x / y = 1.0 / 0.1000something = 9.9999something
  3. При округлении до 0 n фактически становится 9
  4. x - n * y = 1,0 - 9 * 0,1 = 0,1

Редактировать: Что касается того, почему он работает с floor(x/y), насколько я могу судить, это, кажется, причуды FPU.На x86 fmod использует инструкцию fprem, тогда как x/y будет использовать fdiv.Любопытно, что 1.0/0.1, кажется, возвращает ровно 10.0:

>>> struct.pack('d', 1.0/0.1) == struct.pack('d', 10.0)
True

Полагаю, fdiv использует более точный алгоритм, чем fprem.Некоторое обсуждение можно найти здесь: http://www.rapideuphoria.com/cgi-bin/esearch.exu?thread=1&fromMonth=A&fromYear=8&toMonth=C&toYear=8&keywords=%22Remainder%22

1 голос
/ 18 ноября 2010

Этот результат обусловлен машинным представлением с плавающей точкой.В вашем методе вы «приводите» (вроде) float к int и у вас нет этой проблемы.«Лучший» способ избежать таких проблем (особенно для mod) - это умножить на достаточно большой int (в вашем случае требуется только 10) и выполнить операцию снова.(1.0,0.1)

fmod (10.0,1.0) = 0

0 голосов
/ 19 ноября 2010

Здесь полезна функция divmod Python.Он сообщает вам как частное, так и остаток операции деления.

$ python
>>> 0.1
0.10000000000000001
>>> divmod(1.0, 0.1)
(9.0, 0.09999999999999995)

Когда вы набираете 0.1, компьютер не может представить это точное значение в двоичной арифметике с плавающей точкой, поэтому он выбирает ближайшее число, котороеэто может представлять, 0.10000000000000001.Затем, когда вы выполняете операцию деления, арифметика с плавающей точкой решает, что частное должно быть 9, так как 0,10000000000000001 * 10 больше 1,0.Это оставляет вас с остатком чуть меньше 0,1.

Я бы хотел использовать новый модуль Python fractions для получения точных ответов.

>>> from fractions import Fraction
>>> Fraction(1, 1) % Fraction(1, 10)
Fraction(0, 1)

IOW, (1/1) mod (1/10) = (0/1), что эквивалентно 1 mod 0.1 = 0.

Другим вариантом является самостоятельная реализация оператора модуля, позволяющая указать собственную политику.

>>> x = 1.0
>>> y = 0.1
>>> x / y - math.floor(x / y)
0.0
0 голосов
/ 18 ноября 2010

Это дает правильный ответ:

a = 1.0
b = 0.1

a1,a2 = a.as_integer_ratio()
b1,b2 = b.as_integer_ratio()
div = float(a1*b2) / float(a2*b1)
mod = a - b*div
print mod
# 0.0

Я думаю, что это работает, потому что оно использует рациональные эквиваленты двух чисел с плавающей точкой, что дает более точный ответ.

0 голосов
/ 18 ноября 2010

fmod возвращает x-i * y, который меньше y, а i - целое число. 0,09 .... из-за точности с плавающей запятой. попробуйте fmod(0.3, 0.1) -> 0.09..., но fmod(0.4, 0.1) -> 0.0, потому что 0,3 - это 0,2999999 ... как число с плавающей запятой.

fmod(1/(2.**n), 1/(2.**m) никогда не выдаст ничего, кроме 0.0 для целого числа n> = m.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...