Найкращий спосіб змусити модуль 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.