Раунд с целочисленным делением

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

skip = int(round(1.0 * total / surplus))

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

@John: числа с плавающей запятой не воспроизводятся на разных платформах. Если вы хотите, чтобы ваш код проходил тесты на разных платформах, вам нужно избегать операций с плавающей запятой (или добавить в свои тесты какие-нибудь хакерские штуки espilon и надеяться, что это сработает). Вышеупомянутое может быть достаточно простым, чтобы оно было одинаковым на большинстве/всех платформах, но я бы не стал делать такое определение, поскольку проще вообще избежать плавающей запятой. Как это "не в духе Python"?


person gaefan    schedule 16.10.2010    source источник
comment
@John: Ну, longs в Python может хранить произвольно большие значения, где числа с плавающей запятой имеют фиксированную точность, поэтому есть стоимость в диапазоне, сложности и возможных ошибках, связанных с вводом операций с плавающей запятой в целочисленную операцию. Я действительно хотел бы, чтобы люди перестали посыпать каждый вопрос глупым модным словечком Pythonic.   -  person Glenn Maynard    schedule 16.10.2010
comment
@GlennMaynard Верно! Это не очень пифично.   -  person Rob Grant    schedule 20.04.2015


Ответы (6)


Вы можете сделать это довольно просто:

(n + d // 2) // d, где n — делимое, а d — делитель.

Альтернативы, такие как (((n << 1) // d) + 1) >> 1 или эквивалент (((n * 2) // d) + 1) // 2, могут быть МЕДЛЕННЕЕ в последних версиях CPythons, где int реализовано как старое long.

Простой метод выполняет 3 доступа к переменным, 1 постоянную загрузку и 3 операции с целыми числами. Сложные методы выполняют 2 обращения к переменным, 3 операции загрузки констант и 4 операции с целыми числами. Целочисленные операции, вероятно, потребуют времени, которое зависит от размеров задействованных чисел. Доступ к переменным локальных функций не включает «поиски».

Если вам действительно не хватает скорости, сделайте тесты. В противном случае ПОЦЕЛУЙ.

person John Machin    schedule 16.10.2010
comment
+1 Этот метод более читабелен, чем подход со сдвигом битов, а также быстрее (при тестировании timeit) на Py 2.7. - person snapshoe; 17.10.2010

skip = (((total << 1) // surplus) + 1) >> 1

Сдвиг влево на один бит фактически умножает на два, сдвиг вправо на один бит делит на два с округлением в меньшую сторону. Добавление единицы в середине приводит к тому, что «округление в меньшую сторону» на самом деле округляется в большую сторону, если результат был бы выше десятичной части 0,5.

Это в основном то же самое, как если бы вы написали...

skip = int((1.0*total/surplus) + 0.5)

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

person Amber    schedule 16.10.2010
comment
Идея правильная, но я думаю, что количество, которое вам нужно добавить к total, соизмеримо с surplus. Я бы заменил + 1 на + избыток в вашей текущей формуле, и это, вероятно, было бы правильно. - person Pascal Cuoq; 16.10.2010
comment
На самом деле мне просто нужно переместить 1 наружу. :) Это эквивалентно добавлению излишков внутрь, но требует меньше поиска. - person Amber; 16.10.2010
comment
Спасибо! Для меня не сразу очевидно, что это работает правильно во всех пограничных случаях, и я проверю, чтобы быть уверенным. - person gaefan; 16.10.2010
comment
Я бы рекомендовал умножить на два, чтобы умножить на два, и разделить на два, чтобы разделить на два, если только это не профилированный, чувствительный к производительности код. - person Glenn Maynard; 16.10.2010

Вдохновленный ответом zhmyh, который

q, r = divmod(total, surplus)
skip = q + int(bool(r)) # rounds to next greater integer (always ceiling)

, я придумал следующее решение:

q, r = divmod(total, surplus) 
skip = q + int(2 * r >= surplus) # rounds to nearest integer (floor or ceiling)

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

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

Если вам интересно, как определяется divmod: согласно его документации.

Для целых чисел результат такой же, как (a // b, a % b).

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

person Daniel K    schedule 08.04.2014

Еще один забавный способ:

q, r = divmod(total, surplus)
skip = q + int(bool(r))
person rmnff    schedule 25.10.2012
comment
Обратите внимание, что это решение округляется до следующего большего целого числа, которое не обязательно является ближайшим целым числом. См. мой ответ для исправленной версии (которую я разместил в то время, когда у меня еще не было достаточной репутации для комментирования.). - person Daniel K; 03.06.2014

Просто позаботьтесь о правиле округления, прежде чем делить. Для простейшего круглого полувверх:

if total % surplus < surplus / 2:
    return total / surplus
else:
    return (total / surplus) + 1

Немного подправьте, если вам нужно сделать правильное округление до четного.

person hobbs    schedule 16.10.2010
comment
Модуль и оператор деления довольно дороги, этот код выполняет 3 операции деления (одно деление по модулю и 2 обычных деления), поэтому это не оптимально, если код должен быть быстрым. - person FrederikNS; 17.11.2011

Это тоже должно работать:

def rint(n):
    return (int(n+.5) if n > 0 else int(n-.5))
person rubik    schedule 16.10.2010
comment
Это не ответ на вопрос, потому что он включает арифметику с плавающей запятой в n + .5. @rubik Я не минусовал, это был кто-то другой. ;-) - person Arne L.; 30.07.2013
comment
@ArneL.: Ну, это не имеет значения, если это неправильно, правильно поставить минус :) - person rubik; 30.07.2013