Кръгла с целочислено деление

Има ли прост, питоничен начин за закръгляване до най-близкото цяло число без използване на плаваща запетая? Бих искал да направя следното, но с целочислена аритметика:

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 операции с цели числа. Операциите с цели числа вероятно ще отнемат време, което зависи от размера на включените числа. Достъпът до променливи на локалните функции не включва "търсене".

Ако наистина сте отчаяни за скорост, направете бенчмаркове. Иначе KISS.

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).

Следователно ние се придържаме към целочислената аритметика, както се изисква от OP.

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