Найкращий спосіб змусити модуль Java поводитись так, як слід з негативними числами?


103

У Java, коли ти робиш

a % b

Якщо а негативний, він поверне негативний результат замість того, щоб обертатися навколо b, як слід. Який найкращий спосіб це виправити? Єдиний спосіб я можу подумати

a < 0 ? b + a : a % b

12
Немає «правильної» модульної поведінки при роботі з негативними числами - багато мов роблять це так, багато мов роблять це по-різному, а кілька мов роблять щось зовсім інше. Принаймні перші два мають свої плюси і мінуси.

4
це для мене просто дивно. Я думав, що він повинен повертати негатив лише у випадку, якщо b негативний.
fent


2
Це є. але назва цього питання повинна бути перейменована. Я б не натискав на це питання, якби я шукав це, бо я вже знаю, як працює модуль Java.
fent

4
Я просто перейменував це на "Чому дорівнює -13% 64 = 51?", Що ніколи за мільйон років ні за що б хтось не шукав. Таким чином, цей заголовок питання набагато кращий і набагато більше пошуку за такими ключовими словами, як модуль, мінус, обчислення, числа.
Ерік Робертсон

Відповіді:


144

Він поводиться так, як слід,% b = a - a / b * b; тобто це залишок.

Ви можете зробити (% b + b)% b


Цей вираз працює як результат (a % b), обов'язково нижчий за b, незалежно від того, aє він позитивним чи негативним. Додавання bвраховує негативні значення a, оскільки (a % b)негативне значення між -bі 0, (a % b + b)обов'язково нижче, ніж bпозитивне. Останній модуль є на випадок, якщо aдля початку було позитивним, оскільки, якщо aпозитивний, (a % b + b)він стане більшим, ніж b. Тому (a % b + b) % bперетворює його на менший, ніж bзнову (і не впливає на негативні aзначення).


3
це працює краще, дякую. і він працює для від'ємних чисел, значно більших за b.
fent

6
Це працює, оскільки результат (a % b)обов'язково нижчий b(незалежно від того a, позитивний чи негативний), додавання bпіклується про негативні значення a, оскільки (a % b)нижче bі нижче 0, (a % b + b)обов'язково нижче bі позитивних. Останній по модулю є в разі , якщо aбув позитивним для початку, так як якщо aпозитивний (a % b + b)побільшає b. Тому (a % b + b) % bперетворює його на менший, ніж bзнову (і не впливає на негативні aзначення).
ethanfar

1
@eitanfar Я включив у відповідь ваше чудове пояснення (з незначною корекцією a < 0, можливо, ви могли б поглянути)
Maarten Bodewes

5
Я щойно побачив це, коментуючи інше запитання щодо тієї ж теми; Можливо, варто згадати, що (a % b + b) % bрозбивається на дуже великі значення aта b. Наприклад, використання a = Integer.MAX_VALUE - 1і b = Integer.MAX_VALUEдасть -3результат, який є негативним числом, чого ви хотіли уникнути.
Thorbear

2
@Mikepote використовувати whileби було повільніше, якщо вам це справді потрібно, за винятком того, що вам потрібен лише той, ifв якому випадку він насправді швидший.
Пітер Лорі

92

Що стосується Java 8, ви можете використовувати Math.floorMod (int x, int y) та Math.floorMod (довгий х, довгий y) . Обидва ці методи дають ті самі результати, що і відповідь Петра.

Math.floorMod( 2,  3) =  2
Math.floorMod(-2,  3) =  1
Math.floorMod( 2, -3) = -1
Math.floorMod(-2, -3) = -2

1
найкраща відповідь для Java 8+
Charney Kaye

Класно, не знав про це. Java 8 остаточно виправила декілька PITA.
Франц Д.

4
Хороший спосіб. Але , до жаль , не працює з floatабо doubleаргументами. Модний бінарний оператор ( %) також працює floatі doubleоперує.
Мір-Ісмаїлі

11

Для тих, хто ще не використовує (або не може використовувати) Java 8, на допомогу Guava прийшов IntMath.mod () , доступний з Guava 11.0.

IntMath.mod( 2, 3) = 2
IntMath.mod(-2, 3) = 1

Одне застереження: на відміну від Math.floorMod () Java 8, дільник (другий параметр) не може бути негативним.


7

У теорії чисел результат завжди позитивний. Я б здогадався, що це не завжди так у комп’ютерних мовах, оскільки не всі програмісти є математиками. Мої два центи, я вважаю це недоліком дизайну мови, але ви не можете його змінити зараз.

= MOD (-4,180) = 176 = MOD (176, 180) = 176

тому що 180 * (-1) + 176 = -4 те саме, що 180 * 0 + 176 = 176

Використовуючи тут приклад годинника, http://mathworld.wolfram.com/Congruence.html, ви б не сказали, що тривалість_файлу тривалість циклу_довжина становить -45 хвилин, ви б сказали 15 хвилин, хоча обидві відповіді відповідають базовому рівнянню.


1
У теорії чисел це не завжди є позитивним ... Вони потрапляють у класи конгруентності. Ви можете вибирати будь-якого кандидата з цього класу для ваших цілей позначення, але ідея полягає в тому, що він відображає весь цей клас, і якщо використовувати конкретного іншого кандидата, це робить певну проблему значно простішою (вибір -1замість, n-1наприклад,) то майте на це.
BeUndead

2

У Java 8 є Math.floorMod, але це дуже повільно (її реалізація має кілька підрозділів, множень і умовний). Можливо, що JVM має внутрішню оптимізовану заглушку для цього, однак, це значно пришвидшить його.

Найшвидший спосіб зробити це floorMod, як і деякі інші відповіді тут, але без умовних гілок і лише з однією повільною% оп.

Припустимо, що n позитивний, а х може бути будь-що:

int remainder = (x % n); // may be negative if x is negative
//if remainder is negative, adds n, otherwise adds 0
return ((remainder >> 31) & n) + remainder;

Результати, коли n = 3:

x | result
----------
-4| 2
-3| 0
-2| 1
-1| 2
 0| 0
 1| 1
 2| 2
 3| 0
 4| 1

Якщо вам потрібно тільки рівномірний розподіл між 0і n-1і не точний мод оператора, і ваші x«s НЕ кластерний поруч 0, наступний буде ще швидше, так як є більш паралелізм на рівні команд і повільне %обчислення відбуватиметься паралельно з іншими частини, оскільки вони не залежать від її результату.

return ((x >> 31) & (n - 1)) + (x % n)

Результати для вищезазначеного n = 3:

x | result
----------
-5| 0
-4| 1
-3| 2
-2| 0
-1| 1
 0| 0
 1| 1
 2| 2
 3| 0
 4| 1
 5| 2

Якщо вхід є випадковим у повному діапазоні int, розподіл обох двох рішень буде однаковим. Якщо вхідні кластери майже до нуля, результатів n - 1в останньому рішенні буде замало .


1

Ось альтернатива:

a < 0 ? b-1 - (-a-1) % b : a % b

Це може бути або не бути швидшим, ніж інша формула [(a% b + b)% b]. На відміну від іншої формули, вона містить гілку, але використовує одну менш модульну операцію. Можливо, виграш, якщо комп'ютер може правильно передбачити значення <0.

(Редагувати: виправлено формулу.)


1
Але модульна робота вимагає поділу, який може бути навіть повільнішим (особливо якщо процесор майже завжди вгадує гілку). Так що це можливо краще.
Дейв

@KarstenR. Ти правий! Я виправив формулу, тепер вона працює добре (але потребує ще двох віднімань).
Стефан Рейх

Це правда @dave
Стефан Рейх
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.