Может ли кто-нибудь объяснить, как работает оператор modulo в Python?
Я не понимаю, почему 3.5 % 0.1 = 0.1
.
Python modulo на поплавках
Ответ 1
Собственно, это не так, что 3.5 % 0.1
есть 0.1
. Вы можете проверить это очень легко:
>>> print(3.5 % 0.1)
0.1
>>> print(3.5 % 0.1 == 0.1)
False
В действительности, в большинстве систем 3.5 % 0.1
есть 0.099999999999999811
. Но в некоторых версиях Python str(0.099999999999999811)
есть 0.1
:
>>> 3.5 % 0.1
0.099999999999999811
>>> repr(3.5 % 0.1)
'0.099999999999999811'
>>> str(3.5 % 0.1)
'0.1'
Теперь вам, вероятно, интересно, почему 3.5 % 0.1
есть 0.099999999999999811
вместо 0.0
. Это из-за обычных вопросов округления с плавающей запятой. Если вы не читали Что должен знать каждый компьютерный ученый о арифметике с плавающей точкой, вы должны или, по крайней мере, краткое Wikipedia краткое описание этой конкретной проблемы.
Отметим также, что 3.5/0.1
не 34
, it 35
. Итак, 3.5/0.1 * 0.1 + 3.5%0.1
есть 3.5999999999999996
, который даже не близок к 3.5
. Это в значительной степени основополагающее значение для определения модуля, и это неправильно в Python и практически любом другом языке программирования.
Но Python 3 приходит на помощь там. Большинство людей, которые знают о //
, знают, что это то, как вы выполняете "целочисленное деление" между целыми числами, но не понимаете, что это как модульное совместимое разделение между любыми типами. 3.5//0.1
34.0
, поэтому 3.5//0.1 * 0.1 + 3.5%0.1
(по крайней мере, в пределах небольшой ошибки округления) 3.5
. Это было обращено на 2.x, поэтому (в зависимости от вашей точной версии и платформы) вы можете положиться на это. И если нет, вы можете использовать divmod(3.5, 0.1)
, который возвращает (в пределах ошибки округления) (34.0, 0.09999999999999981)
весь путь обратно в туман времени. Конечно, вы все еще ожидали, что это будет (35.0, 0.0)
, а не (34.0, almost-0.1)
, но вы не можете этого сделать из-за ошибок округления.
Если вы ищете быстрое исправление, попробуйте использовать Decimal
тип:
>>> from decimal import Decimal
>>> Decimal('3.5') % Decimal('0.1')
Decimal('0.0')
>>> print(Decimal('3.5') % Decimal('0.1'))
0.0
>>> (Decimal(7)/2) % (Decimal(1)/10)
Decimal('0.0')
Это не волшебная панацея - например, вам все равно придется иметь дело с ошибкой округления всякий раз, когда точное значение операции не является окончательно представимым в базе 10, - но ошибки округления лучше совпадают с случаями человеческая интуиция предполагает быть проблематичной. (Есть также преимущества Decimal
над float
в том, что вы можете указать явные уточнения, отслеживать значительные цифры и т.д., И тем, что он фактически одинаковый во всех версиях Python от 2.4 до 3.3, а подробности о float
изменились дважды в одно и то же время. Просто это не идеально, потому что это было бы невозможно.) Но когда вы заранее знаете, что ваши цифры точно представлены в базе 10, и им не нужно больше цифр, чем точность вы настроили его, он будет работать.
Ответ 2
Modulo дает вам rest
деления. 3.5
, деленный на 0.1
, должен дать вам 35
с остальной частью 0
. Но поскольку поплавки основаны на степенях двух чисел, они не точны и вы получаете ошибки округления.
Если вам нужно деление десятичных чисел на точное использование десятичного модуля:
>>> from decimal import Decimal
>>> Decimal('3.5') / Decimal('0.1')
Decimal('35')
>>> Decimal('3.5') % Decimal('0.1')
Decimal('0.0')
Поскольку меня избивают, что мой ответ вводит в заблуждение, здесь идет вся история:
0.1
немного больше, чем 0.1
>>> '%.50f' % 0.1
'0.10000000000000000555111512312578270211815834045410'
Если вы разделите float 3.5
на такое число, вы получите остаток почти 0.1
.
Начните с номера 0.11
и продолжайте добавлять нули между двумя цифрами 1
, чтобы уменьшить его, сохраняя его больше, чем 0.1
.
>>> '%.10f' % (3.5 % 0.101)
'0.0660000000'
>>> '%.10f' % (3.5 % 0.1001)
'0.0966000000'
>>> '%.10f' % (3.5 % 0.10001)
'0.0996600000'
>>> '%.10f' % (3.5 % 0.100001)
'0.0999660000'
>>> '%.10f' % (3.5 % 0.1000001)
'0.0999966000'
>>> '%.10f' % (3.5 % 0.10000001)
'0.0999996600'
>>> '%.10f' % (3.5 % 0.100000001)
'0.0999999660'
>>> '%.10f' % (3.5 % 0.1000000001)
'0.0999999966'
>>> '%.10f' % (3.5 % 0.10000000001)
'0.0999999997'
>>> '%.10f' % (3.5 % 0.100000000001)
'0.1000000000'
В последней строке создается впечатление, что мы наконец достигли 0.1
, но изменение строк формата показывает истинную природу:
>>> '%.20f' % (3.5 % 0.100000000001)
'0.09999999996600009156'
Формат float по умолчанию для python просто не показывает достаточную точность, поэтому 3.5 % 0.1 = 0.1
и 3.5 % 0.1 = 35.0
. Это действительно 3.5 % 0.100000... = 0.999999...
и 3.5 / 0.100000... = 34.999999....
. В случае разделения вы даже получаете точный результат, так как 34.9999...
округляется до 35.0
.
Забавный факт: если вы используете число, которое немного меньше, чем 0.1
, и выполните ту же операцию, вы получите число, которое немного больше, чем 0
:
>>> 1.0 - 0.9
0.09999999999999998
>>> 35.0 % (1.0 - 0.9)
7.771561172376096e-15
>>> '%.20f' % (35.0 % (1.0 - 0.9))
'0.00000000000000777156'
С помощью С++ вы можете даже показать, что 3.5
, деленное на float 0.1
, не 35
, но немного меньше.
#include <iostream>
#include <iomanip>
int main(int argc, char *argv[]) {
// double/float, rounding errors do not cancel out
std::cout << "double/float: " << std::setprecision(20) << 3.5 / 0.1f << std::endl;
// double/double, rounding errors cancel out
std::cout << "double/double: " << std::setprecision(20) << 3.5 / 0.1 << std::endl;
return 0;
}
В Python 3.5 / 0.1
вы получите точный результат 35
, потому что ошибки округления компенсируют друг друга. Это действительно 3.5 / 0.100000... = 34.9999999...
. И 34.9999...
предельно длинный, так что вы получите именно 35
. Программа С++ показывает это хорошо, поскольку вы можете смешивать double и float и играть с точностью до чисел с плавающей запятой.
Ответ 3
Это связано с неточным характером арифметики с плавающей запятой. 3.5 % 0.1
получает меня 0.099999999999999811
, поэтому Python думает, что 0.1 делится на 3.5 не более 34 раз, оставив 0.099999999999999811. Я не уверен, какой алгоритм используется для достижения этого результата, но это суть.