В чем причина различия между целочисленным делением и преобразованием с плавающей точкой в ​​int в python?

52

Я недавно заметил, что int()округляет число с плавающей точкой до 0, а целочисленное деление округляет число с плавающей точкой до его пола.

например:

-7 // 2 = -4
int(-7/2) = -3

Я прочитал документацию, в которой указано:

класс int (x, base = 10)

Вернуть целочисленный объект, построенный из числа или строки x, или вернуть 0, если аргументы> не заданы. Если x - это число, верните x. int (). Для чисел с плавающей запятой это усекается до нуля.

а также:

этажное подразделение

Математическое деление, которое округляется до ближайшего целого числа. Оператор подразделения этажа //. Например, выражение 11 // 4 оценивается как 2 в отличие от 2.75, возвращаемого делением с плавающей запятой. Обратите внимание, что (-11) // 4 равно -3, потому что это -2,75 с округлением вниз. Смотри PEP 238.

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

Есть ли мотивация для различий между функциями?

Спасибо.

IsaacDj
источник
Соответствующая ссылка: python-history.blogspot.com/2010/08/…
dan04

Ответы:

61

Согласованность.

Вам нужно будет следовать некоторым очень простым и, казалось бы, нерелевантным объяснениям, чтобы понять это.

В школе вы выучили деление с остатком. И вы сделали расчеты следующим образом:

8 ÷ 4 = 2 R 0
7 ÷ 4 = 1 R 3
6 ÷ 4 = 1 R 2
5 ÷ 4 = 1 R 1
4 ÷ 4 = 1 R 0
3 ÷ 4 = 0 R 3
2 ÷ 4 = 0 R 2
1 ÷ 4 = 0 R 1
0 ÷ 4 = 0 R 0
        ^------ This is the result of x // 4
            ^-- This is the result of x % 4 (modulo)

Позже вы выучили деления на действительные числа:

8 ÷ 4 = 2.0
7 ÷ 4 = 1.75
6 ÷ 4 = 1.5
5 ÷ 4 = 1.25
4 ÷ 4 = 1.0
3 ÷ 4 = 0.75
2 ÷ 4 = 0.5
1 ÷ 4 = 0.25
0 ÷ 4 = 0.0
        ^--- Note that the number in front of the . is int(x/4)

До этого момента вы можете в это поверить x // 4и int(x/4)всегда давать один и тот же результат. Это ваше текущее понимание ситуации.

Однако посмотрите, что происходит при целочисленном делении: число за R циклически изменяется от 3, 2, 1 до 0, а затем перезапускается: 3, 2, 1, 0. Число перед R уменьшается с каждым 4-м шагом.

Итак, как это будет продолжаться?

 8 ÷ 4 =  2 R 0
 7 ÷ 4 =  1 R 3
 6 ÷ 4 =  1 R 2
 5 ÷ 4 =  1 R 1
 4 ÷ 4 =  1 R 0
 3 ÷ 4 =  0 R 3
 2 ÷ 4 =  0 R 2
 1 ÷ 4 =  0 R 1
 0 ÷ 4 =  0 R 0
-1 ÷ 4 = -1 R 3
         ^------ We have to decrease now, because we already have 0 four times
              ^-- We have to restart the cycle at 3

В то же время деление действительных чисел дает нам:

-1 ÷ 4 = -0.25
          ^----- There is still a 0 in front of the .

Вот почему -1 // 4дает -1, но int(-1/4)дает 0.

Есть ли мотивация для различий между функциями?

Ну, они служат различным целям: //это часть вычисления целых чисел с остатками и int()дает вам часть перед .операцией с действительным числом.

Вы решаете, что вы хотите вычислить, затем вы решаете, какой оператор использовать в Python, чтобы получить правильный результат.

Хороший вопрос. Продолжайте учиться.

Томас Веллер
источник
11
На практике это учитывает хитрость: если у вас есть -1 конфета, и вы отдаете ее 4 друзьям, то останется 3 сладости. Отлично, не правда ли? Вам нужно только узнать, как владеть -1 сладостью.
Томас Веллер
1
Это создает некоторую согласованность, но, насколько я понимаю, мотивация в добавлении //оператора в Python 3 заключается в том, чтобы избежать принудительного использования int (float). Если это не так, когда я должен реализовать использование int()и когда я должен реализовать использование//
IsaacDj
1
Хорошо, тогда это просто неверное предположение. В этом нет ничего плохого, если вы проверяете свои предположения на правильность, что, вероятно, не удается в 50% случаев (по крайней мере, для меня). Я добавил несколько слов об этом в ответ.
Томас Веллер
2
@IsaacDj, возможно, вы захотите прочитать об этом историю оператора «деление по полу».
Bruno Desthuilliers
1
@EricLippert: я не думаю, что это странно. Мы не можем предположить, что операция с потерями дает тот же результат, что и для точной операции. Говорят в коде: по Math.Floor(3.23) != -Math.Floor(-3.23)той же причине -((-x)//y)не обязательно равны x//y.
Томас Веллер
4

Я бы сказал, что ваше наблюдение о том, что эти 2 операции должны быть интуитивно схожими, ожидается, поскольку при положительных числах они ведут себя одинаково. Но если вы посмотрите на их происхождение (одно исходит из математики, а другое из информатики), тогда становится более понятным их различное поведение.

Вы можете посмотреть там концепции:

  • Этажное деление или функция пола, применяемое к математическому разделению
  • Тип преобразования / Тип литья

================================================== ================

I) Этажное деление или функция пола, применяемое к математическому разделению

Функция пола является очень хорошо зарекомендовавшим себя понятием в математике.

От mathworld.wolfram :

Функция основания | _ x_ |, также называемая наибольшей целочисленной функцией или целочисленным значением (Spanier and Oldham 1987), дает наибольшее целое число, меньшее или равное x. Название и символ функции пола были придуманы К.Е. Айверсоном (Грэм и др., 1994).

Таким образом, разделение по этажам - это не более, чем функция по этажам, применяемая к математическому разделению. Поведение очень ясное, «математически точное».

II) Тип преобразования / Тип литья

Из википедии :

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

В большинстве языков программирования приведение формы с плавающей точкой к целому применяется правилом округления (так что существует соглашение):

  • Округление к 0 - направленное округление к нулю (также известное как усечение)

Правило округления согласно IEEE 754 .


Итак, другими словами, причина различия между целочисленным делением и преобразованием с плавающей точкой в ​​int в python является математической, вот некоторые соображения от Гвидо ван Россума (думаю, мне не нужно его представлять: D) (из блог История Python, статья «Почему полы целочисленного деления Python» )

Некоторых это беспокоит, но есть веская математическая причина. Операция целочисленного деления (//) и ее брат, операция по модулю (%) идут вместе и удовлетворяют хорошим математическим отношениям (все переменные являются целыми числами):

a / b = q с остатком r

такой, что

b * q + r = a и 0 <= r <b

(при условии, что a и b>> 0).

kederrac
источник