Почему целочисленное деление округляется во многих языках сценариев?

на языках, которые я испытал, - (x div y ) не равно -x div y; Я испытал // в Python, / в Ruby, div в Perl 6; C имеет аналогичное поведение.

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

есть ли какое-то конкретное обоснование этого проектного решения, или это просто div определяется таким образом с нуля? Видимо Гвидо ван Россум использует аргумент когерентность в блоге, который объясняет, как это делается в Python, но вы также можете иметь согласованность, если вы решите округлить.

(вдохновленный этот вопрос PMurias в IRC-канале #perl6)

5 ответов


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

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

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

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

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


в идеале, мы хотели бы получить две операции div и mod, сытно, для каждого b>0:

  1. (a div b) * b + (a mod b) = a
  2. 0 <= (a mod b) < b
  3. (-a) div b = -(a div b)
1 div 2 = 0
1 mod 2 = 1

поскольку это уникальное целочисленное решение для (1) и (2). Следовательно, мы, по (3),

0 = -0 = -(1 div 2) = (-1) div 2

что, на (1), подразумевает

-1 = ((-1) div 2) * 2 + ((-1) mod 2) = 0 * 2 + ((-1) mod 2) = (-1) mod 2

делая (-1) mod 2 < 0 что противоречит (2).

некоторые языки программирования отказываются от (3) и делают div округлить вниз (Python, Ruby).

в некоторых (редких) случаях язык предлагает несколько операторов отдела. Например, в Haskell у нас есть div,mod удовлетворяя только (1) и (2), аналогично Python, и у нас также есть quot,rem удовлетворяет только (1) и (3). Последняя пара операторов округляет деление к нулю, по цене возврата отрицательных остатков, например, у нас есть (-1) `quot` 2 = 0 и (-1) `rem` 2 = (-1).

C# также отказывается от (2) и позволяет % для возврата отрицательного остатка. Когерентно целочисленное деление округляется до нуля. Java, Scala, Pascal и C, начиная с C99, также принимают эту стратегию.


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


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


пока язык удовлетворяет свойству евклидова деления, которое (a/b) * b + (a%b) == a, и разделение настила и усекая разделение когерентны и арифметически здравы.


%, хотя, вероятно, имеет смысл выбрать / сначала, а затем просто выберите %, который соответствует.
  • пол (вроде Python):
    • не менее авторитетный, чем Дональд Кнут, предлагает это.
    • % после знака делителя, по-видимому, около 70% все студенты угадать
    • оператор обычно читается как mod или modulo, а не remainder.
    • "C делает это" - что даже не правда.1
  • усечение (например, C++):
    • делает целочисленное деление более совместимым с разделением IEEE float (в режиме округления по умолчанию).
    • больше процессоров реализовать его. (Возможно, это не так в разные периоды истории.)
    • оператор читать modulo а не remainder (хотя это на самом деле доказывает против их точка зрения).
    • свойство деления концептуально больше относится к остатку, чем к модулю.
    • оператор читать mod, а не modulo, поэтому он должен следовать различию Фортрана. (Это может показаться глупым, но, возможно, было решающим фактором для C99. См.этой теме.)
  • "Евклидово" (как Паскаль -/ полы или усечения в зависимости от знаков, так % никогда не отрицательной):
    • Никлаус Вирт утверждал, что никто никогда не удивлены положительным mod.
    • Раймонд т. Буте позже утверждал, что вы не можете наивно реализовать евклидово деление ни с одним из других правил.

несколько языков обеспечивают оба. Обычно-как в Ada, Modula-2, некоторых Lisps, Haskell и Julia-они используют имена, связанные с mod для оператора в стиле Python и rem для оператора в стиле C++. Но не всегда-Фортран, например, называет одно и то же modulo и mod (как упоминалось выше для C99).


мы не знаем, почему Python, Tcl, Perl и другие влиятельные скриптовые языки в основном выбрали напольное покрытие. Как отмечалось в вопросе, ответ Гвидо ван Россума объясняет только то, почему он должен был выбрать один из трех последовательных ответов, а не почему он выбрал тот, который он сделал.

однако я подозреваю влияние C являться ключевыми. Большинство языков сценариев (по крайней мере, изначально) реализованы на C и заимствуют свой инвентарь операторов из реализации C. C89, определенной % очевидно сломан и не подходит для "дружественного" языка, такого как Tcl или Python. И C вызывает оператора "mod". Поэтому они идут с модулем, а не с остатком.


1. Несмотря на то, что вопрос Говорит-и многие люди используют его в качестве аргумента-C на самом деле не имеют аналогичное поведение с Python и друзья. C99 требует усечения деления, а не настила. C89 разрешил либо, а также разрешил любую версию mod, поэтому нет никакой гарантии свойства division, и нет способа написать портативный код, выполняющий целочисленное деление со знаком. Это просто сломано.


как сказала Паула, это из-за остального.

алгоритм основан на Евклидово подразделение.

в Ruby, вы можете написать это восстановление дивидендов с последовательностью:

puts (10/3)*3 + 10%3
#=> 10

это работает в реальной жизни. 10 яблок и 3 человек. Хорошо, вы можете разрезать одно яблоко на три, но выходя за пределы заданных целых чисел.

с отрицательными числами последовательность также сохраняется:

puts (-10/3)*3 + -10%3 #=> -10
puts (10/(-3))*(-3) + 10%(-3) #=> 10
puts (-10/(-3))*(-3) + -10%(-3) #=> -10

коэффициент всегда округлите вниз (вниз по отрицательной оси), и напоминание следует:

puts (-10/3) #=> -4
puts -10%3 #=> 2

puts (10/(-3)) #=> -4
puts 10%(-3) # => -2

puts (-10/(-3)) #=> 3
puts -10%(-3) #=> -1