Лучший способ заставить модуль Java вести себя так, как должен, с отрицательными числами?

В java, когда вы делаете

a % b

Если a отрицательно, он вернет отрицательный результат, вместо того, чтобы оборачиваться до b, как должно. Как лучше всего это исправить? Я могу думать только так

a < 0 ? b + a : a % b

person fent    schedule 10.12.2010    source источник
comment
При работе с отрицательными числами нет правильного модульного поведения - многие языки делают это так, многие языки делают это по-другому, а некоторые языки делают что-то совершенно другое. По крайней мере, у первых двух есть свои плюсы и минусы.   -  person    schedule 10.12.2010
comment
для меня это просто странно. Я думал, что он должен возвращать отрицательный результат только в том случае, если b отрицательно.   -  person fent    schedule 10.12.2010
comment
Это. но название этого вопроса следует переименовать. Я бы не стал нажимать на этот вопрос, если бы искал его, потому что я уже знаю, как работает модуль java.   -  person fent    schedule 10.12.2010
comment
Я просто переименовал его в «Почему -13% 64 = 51?», Который никогда и за миллион лет не станет чем-то, что можно было бы найти. Таким образом, этот заголовок вопроса намного лучше, и его гораздо легче искать по таким ключевым словам, как модуль, отрицательный, расчет, числа.   -  person Erick Robertson    schedule 10.12.2010


Ответы (6)


Он ведет себя так, как должен a% b = a - a / b * b; т.е. это остаток.

Вы можете сделать (a% 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 значения).

person Peter Lawrey    schedule 10.12.2010
comment
это работает лучше, спасибо. и это работает и для отрицательных чисел, которые намного больше, чем b. - person fent; 10.12.2010
comment
это действительно хороший ответ! так близко к элегантному, как java позволит вам уйти, я думаю - person mfrankli; 25.10.2012
comment
@PeterLawrey - Спасибо, мне тоже нужно было это решение, но не могли бы вы объяснить, почему оно работает? - person Lou; 12.04.2014
comment
Это работает, поскольку результат (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 значения). - person ethanfar; 13.04.2014
comment
@eitanfar Я включил в ответ ваше превосходное объяснение (с незначительной поправкой на a < 0, может быть, вы могли бы взглянуть) - person Maarten Bodewes; 13.09.2014
comment
Я только что видел, как это комментировали по другому вопросу, касающемуся той же темы; Возможно, стоит упомянуть, что (a % b + b) % b не работает при очень больших значениях a и b. Например, использование a = Integer.MAX_VALUE - 1 и b = Integer.MAX_VALUE даст результат -3, что является отрицательным числом, чего вы хотели избежать. - person Thorbear; 16.09.2015
comment
Разве выполнение (a % b + b) % b не намного медленнее, чем a = a % b; while (a < 0) { a += b;}? Поскольку вам нужно выполнить две операции по модулю? (Предполагая, что вас интересуют только случаи, когда a ‹= b) - person Mikepote; 08.03.2016
comment
@Mikepote, использующий while, будет медленнее, если он вам действительно нужен, за исключением того, что вам нужен только if, и в этом случае он действительно быстрее. - person Peter Lawrey; 08.03.2016
comment
@PeterLawrey У меня есть вопрос, здесь, в котором hashCode метод String возвращает отрицательное значение bcoz, из которого мое значение индекса становится отрицательным. Я хотел посмотреть, сможешь ли ты помочь. - person arsenal; 19.04.2016
comment
Как насчет включения решения (без условия) для тех, кому также нужно частное от деления по модулю (например, -1 div 4 = -1 вместо -1 / 4 = 0)? Вот он (при условии, что у вас уже есть модуль в переменной mod_ab): (a - (mod_ab - a % b)) / b. - person Rômulo Ceccon; 17.03.2017
comment
Одно маленькое замечание: если a находится на b расстоянии от 0 (например, при переносе экрана некоторой обновленной координаты x a на 2D-карту игры шириной b), (a + b) % b подойдет. - person runcoderun; 17.10.2018
comment
Совершенно уверен, что по модулю и остаток - это не одно и то же. Вы можете найти определения в справочнике по математике, но% означает по модулю на одних языках и остаток на других. stackoverflow.com/ вопросы / 40692016 / - person Chris Golledge; 10.04.2021

Начиная с Java 8, вы можете использовать Math.floorMod (int x, int y) и Math.floorMod (long x, long y). Оба эти метода возвращают те же результаты, что и ответ Питера.

Math.floorMod( 2,  3) =  2
Math.floorMod(-2,  3) =  1
Math.floorMod( 2, -3) = -1
Math.floorMod(-2, -3) = -2
person John Krueger    schedule 14.09.2014
comment
лучший ответ для Java 8+ - person Charney Kaye; 13.01.2018
comment
Круто, не знал об этом. В Java 8 окончательно исправлены некоторые ошибки PITA. - person Franz D.; 04.06.2018
comment
Хороший способ. Но, к сожалению, не работает с float или double аргументами. Бинарный оператор Mod (%) также работает с операндами float и double. - person Mir-Ismaili; 07.06.2018

Тем, кто еще не использует (или не может использовать) Java 8, на помощь приходит Guava с IntMath.mod (), доступный с Guava 11.0.

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

Одно предостережение: в отличие от Java 8 Math.floorMod (), делитель (второй параметр) не может быть отрицательным.

person Ibrahim Arief    schedule 25.03.2016

В теории чисел результат всегда положительный. Я предполагаю, что это не всегда так в компьютерных языках, потому что не все программисты математики. Мои два цента, я бы посчитал это конструктивным дефектом языка, но вы не можете его сейчас изменить.

= МОД (-4,180) = 176 = МОД (176, 180) = 176

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

Используя здесь пример часов, http://mathworld.wolfram.com/Congruence.html вы бы не говорите, что duration_of_time mod cycle_length составляет -45 минут, вы бы сказали 15 минут, даже если оба ответа удовлетворяют базовому уравнению.

person Chris Golledge    schedule 09.06.2016
comment
В теории чисел это не всегда положительно ... Они попадают в классы конгруэнтности. Вы можете выбрать любого кандидата из этого класса для своих целей нотации, но идея состоит в том, что он отображается на все этого класса, и если использование конкретного другого кандидата из него значительно упрощает определенную проблему (например, выбирая -1 вместо n-1), тогда действуйте. - person BeUndead; 10.07.2019
comment
Определенно случай с программистом-примадонной, который думает, что знает лучше, чем все остальные, и просто вызывает замешательство у всех. Для остатка следовало использовать оператор, отличный от%, который традиционно возвращал значения от 0 до модуля. Это может вызвать труднодоступные ошибки и такие вещи, как переворачивание самолетов при пересечении экватора. Обратите внимание, что я не предлагаю его сейчас менять. - person Richard Thomas; 10.11.2020

В Java 8 есть Math.floorMod, но он очень медленный (его реализация имеет множественное деление, умножение и условное выражение). Однако вполне возможно, что JVM имеет внутреннюю оптимизированную заглушку, которая значительно ее ускорит.

Самый быстрый способ сделать это без floorMod похож на некоторые другие ответы здесь, но без условных переходов и только с одной медленной % op.

Предполагая, что n положительно, а x может быть любым:

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 не кластеризуются рядом с 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.

person Scott Carey    schedule 01.10.2018
comment
Кажется, предполагается, что int - 32 бита. Что, вероятно, в основном безопасно, но на сайте Java указано int: по умолчанию тип данных int представляет собой 32-битное целое число с дополнением до двух со знаком. По умолчанию это звучит немного небрежно. [Edit: копаем дальше, похоже, что в спецификации указаны твердые 32 бита, так что продолжайте] - person Richard Thomas; 10.11.2020

Вот альтернатива:

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

Это может быть или не быть быстрее, чем другая формула [(a% b + b)% b]. В отличие от другой формулы, она содержит ветвь, но использует на одну операцию по модулю меньше. Вероятно, выигрыш, если компьютер сможет правильно предсказать <0.

(Изменить: исправлена ​​формула.)

person Stefan Reich    schedule 31.01.2017
comment
Но операция по модулю требует деления, которое может быть еще медленнее (особенно, если процессор почти все время правильно угадывает ветвь). Так что, возможно, лучше. - person dave; 03.08.2018
comment
@KarstenR. Ты прав! Я исправил формулу, теперь она работает нормально (но нужно еще два вычитания). - person Stefan Reich; 24.08.2018
comment
Это правда @dave - person Stefan Reich; 07.08.2020