2010-10-16 5 views
18

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

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

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

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

+7

@John: Ну, longs в Python могут хранить произвольно большие значения, где float фиксированной точности, поэтому есть стоимость в диапазоне, сложности и возможных ошибок, представляющих с плавающей запятой целочисленную операцию. Хотелось бы, чтобы люди переставали разбрызгивать каждый вопрос глупым словом «Pythonic». –

+0

@GlennMaynard True! Это не очень Pythical. –

ответ

28

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

(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 целых операции. Целые операции, вероятно, потребуют времени, которое зависит от размеров задействованных чисел. Переменные обращения к локальным функциям не связаны с поиском.

Если вы действительно desparate для скорости, делайте тесты. В противном случае, KISS.

+2

+1 Этот метод является более читаемым, чем метод смещения бит, а также быстрее (при тестировании времени) на Py 2.7. – snapshoe

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

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

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

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

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

+0

Правильная идея, но я думаю, что количество, которое нужно добавить в 'total', соизмеримо с' излишком'. Я бы заменил «+ 1» на «+ излишек» в вашей текущей формуле, и это, вероятно, будет правильным. –

+0

На самом деле мне просто нужно переместить 1 снаружи. :) Это эквивалентно добавлению излишков внутри, но требует меньше поиска. – Amber

+0

да, это другой возможность. –

-2

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

def rint(n): 
    return (int(n+.5) if n > 0 else int(n-.5)) 
+0

Это не ответ на вопрос, потому что он включает арифметику с плавающей запятой в 'n + .5'. @rubik Я не спустил вниз, это был кто-то еще. ;-) –

+1

@ArneL: Ну, это не имеет значения, если это неправильно, это правильно для downvote :) – rubik

0

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

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

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

+1

Оператор modulo и оператор деления довольно дороги, этот код запускает операции с тремя делениями (по одному модулю и 2 регулярным делениям), поэтому это не оптимально, если код должен быть быстрым. – FrederikNS

2

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

q, r = divmod(total, surplus) 
skip = q + int(bool(r)) 
+0

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

4

Вдохновленный zhmyh's answer ответ, который

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) 

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

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

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

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

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