Модульна операція з від’ємними числами


194

У програмі C я намагався виконувати наведені нижче дії (Просто для перевірки поведінки)

 x = 5 % (-3);
 y = (-5) % (3);
 z = (-5) % (-3); 

printf("%d ,%d ,%d", x, y, z); 

дав мені вихід, як (2, -2 , -2)у GCC. Я щоразу очікував позитивного результату. Чи може модуль бути негативним? Хтось може пояснити таку поведінку?


Можливий дублікат stackoverflow.com/questions/4003232/…
james

Відповіді:


170

C99 вимагає , коли він a/bє представницьким:

(a/b) * b + a%b дорівнюєa

Це має сенс, логічно. Правильно?

Подивимось, до чого це призводить:


Приклад А. 5/(-3)є-1

=> (-1) * (-3) + 5%(-3) =5

Це може статися лише за умови, що 5%(-3)це 2.


Приклад B. (-5)/3є-1

=> (-1) * 3 + (-5)%3 =-5

Це може статися тільки тоді , коли (-5)%3це-2


1
Чи повинен компілятор бути досить розумним і виявляти, що без підписаного модуля інший неподписаний завжди позитивний? В даний час (добре, GCC 5.2) компілятор, здається, вважає, що "%" повертає "int" в цьому випадку, а не "непідписаний", навіть коли обидва операнди uint32_t або більше.
Фредерік Норд

@FrederickNord Чи є у вас приклад, щоб показати таку поведінку ?
chux

10
Зрозумійте, що те, що ви описуєте, - це звичайний int (a / b) (усікання) опис моди. Але також можливо, що правило є пол (a / b) (Knuth). У випадку Knuth -5/3є, -2а мод стає 1. Коротше кажучи: один модуль має знак, який слідує за знаком дивіденду (усікає), інший модуль має знак, що слідує за знаком дільника (Knuth).
Ісаак

1
Це випадок стандарту С, який точно не такий, який я хочу. Я ніколи не хотів усікати нульові або від’ємні числа модулів, але часто хочу навпаки і мені потрібно обійти С.
Джо

143

%Оператор в C НЕ по модулю оператора , але залишковий оператор.

Оператори модулів та залишків відрізняються щодо негативних значень.

Для оператора, що залишився, знак результату такий самий, як і знак дивіденду, тоді як для оператора модуля знак результату такий самий, як і дільник.

C визначає %операцію для a % b:

  a == (a / b * b) + a % b

з /цілим поділом з усіченням у бік 0. Це усічення, яке робиться у напрямку 0(а не до негативної нескінченності), що визначає %як оператор залишку, а не оператор модуля.


8
Залишок є результатом роботи модуля за визначенням. Не повинно бути такого, як оператор залишку, тому що немає такої речі, як операція залишку, це називається modulo.
gronostaj

41
@gronostaj не в CS. Подивіться на мови вищого рівня, як Haskell або Scheme, які обидва визначають два різних операторів ( remainderі moduloв Scheme, remі modв Haskell). Ці специфікації операторів у цих мовах відрізняються тим, як здійснюється поділ: усічення до 0 або до негативної нескінченності. До речі C стандарт ніколи не називає оператор по модулю , вони просто назвати це оператор% . %
оуа

2
Не плутати з remainder функцією в C, яка реалізує залишок IEEE з округлою до найближчої семантики в підрозділі
Ерік

68

На основі специфікації C99: a == (a / b) * b + a % b

Ми можемо написати функцію для обчислення (a % b) == a - (a / b) * b!

int remainder(int a, int b)
{
    return a - (a / b) * b;
}

Для модульної роботи ми можемо мати таку функцію (припускаючи b > 0)

int mod(int a, int b)
{
    int r = a % b;
    return r < 0 ? r + b : r;
}

Мій висновок полягає a % bв тому, що в С - це операція, що залишилася, а НЕ модульна операція.


3
Це не дає позитивних результатів, коли bє негативним (а насправді rі для bобох, і для негативних, це дає результати менше, ніж -b). Щоб забезпечити позитивні результати для всіх вхідних даних, які ви можете використовувати, r + abs(b)або щоб відповідати bзнаку s, ви можете замінити умову r*b < 0.
Мартін Ендер

@MartinEnder r + abs(b)- це UB, коли b == INT_MIN.
chux

60

Я не думаю, що немає необхідності перевіряти, чи число негативне.

Проста функція пошуку позитивного модуля була б така -

Редагувати: припускаючи N > 0іN + N - 1 <= INT_MAX

int modulo(int x,int N){
    return (x % N + N) %N;
}

Це буде працювати як для позитивних, так і для негативних значень x.

Оригінальний PS: також, як вказував @chux, Якщо ваші х і N можуть досягти чогось типу INT_MAX-1 і INT_MAX відповідно, просто замініть intнаlong long int .

І якщо вони також перетинають межі великої довжини (тобто поблизу LLONG_MAX), тоді ви можете розбирати позитивні та негативні випадки окремо, як описано в інших відповідях тут.


1
Зауважте, що коли N < 0результат може бути негативним як у modulo(7, -3) --> -2. Також x % N + Nможе переповнювати intматематику, що є невизначеною поведінкою. наприклад, це modulo(INT_MAX - 1,INT_MAX)може призвести до -3.
chux

Так, у такому випадку ви можете просто використовувати long long intабо обробляти негативний випадок окремо (ціною втрати простоти).
Удайрай Дешмух

9

Інші відповіді пояснили в C99 або пізніших версіях, поділ цілих чисел, що включають негативні операнди, завжди скорочується до нуля .

Зауважте, що в C89 , чи визначається результат раунду вгору чи вниз, визначено реалізацією. Оскільки (a/b) * b + a%bрівний aу всіх стандартах, результат %залучення негативних операндів також визначений в реалізації C89.


5

Чи може модуль бути негативним?

%може бути негативним, оскільки це оператор , що залишився, решта після поділу, а не після Евклідового_розділу . Оскільки С99 результат може бути 0, негативним чи позитивним.

 // a % b
 7 %  3 -->  1  
 7 % -3 -->  1  
-7 %  3 --> -1  
-7 % -3 --> -1  

За модулю OP хотів класичний евклідової по модулю , що не %.

Я щоразу очікував позитивного результату.

Виконання евклідового модуля, який є чітко визначеним щоразу, коли a/bвизначено, a,bє будь-якими ознаками, і результат ніколи не буде негативним:

int modulo_Euclidean(int a, int b) {
  int m = a % b;
  if (m < 0) {
    // m += (b < 0) ? -b : b; // avoid this form: it is UB when b == INT_MIN
    m = (b < 0) ? m - b : m + b;
  }
  return m;
}

modulo_Euclidean( 7,  3) -->  1  
modulo_Euclidean( 7, -3) -->  1  
modulo_Euclidean(-7,  3) -->  2  
modulo_Euclidean(-7, -3) -->  2   

2

Результат роботи модуля залежить від знака чисельника, і, таким чином, ви отримаєте -2 для y і z

Ось посилання

http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_14.html

Цілий відділ

У цьому розділі описані функції для виконання цілого поділу. Ці функції є зайвими в бібліотеці GNU C, оскільки в GNU C оператор '/' завжди округляється до нуля. Але в інших реалізаціях C '/' може по-різному округлятись негативними аргументами. div та ldiv корисні, оскільки вони вказують, як округлити показник: до нуля. Решта має той самий знак, що і чисельник.


5
Ви посилаєтесь на текст про ANSI C. Це досить стара норма C. Не впевнений, чи правильний текст щодо ANSI C, але точно не щодо C99. У С99 §6.5.5 визначається ціле ділення, яке завжди скорочується до нуля.
Палець

2

У математиці, звідки випливають ці умовності, немає твердження, що арифметична модуль повинна дати позитивний результат.

Напр.

1 мод 5 = 1, але він також може дорівнювати -4. Тобто 1/5 дає залишок 1 від 0 або -4 від 5. (Обидва коефіцієнта по 5)

Аналогічно, -1 mod 5 = -1, але він також може дорівнювати 4. Тобто -1/5 дає залишок -1 від 0 або 4 від -5. (Обидва коефіцієнта 5)

Для подальшого читання розгляньте класи еквівалентності з математики.


Клас еквівалентності - це інше поняття, і модуль визначається дуже суворо. Скажімо , у нас є два цілих числа aі b, b <> 0. Відповідно до теореми Евклідового поділу існує рівно одна пара цілих чисел m, rде a = m * b + rі 0 <= r < abs( b ). Зазначене rє результатом (математичної) модульної операції і за визначенням є негативним. Більше читання та подальші посилання на Wikipedia: en.wikipedia.org/wiki/Euclidean_division
Істер

Це неправда. 1 mod 5завжди 1. -4 mod 5може бути і 1, але це різні речі.
FelipeC

2

Відповідно до стандарту C99 , розділ 6.5.5 Мультиплікаційні оператори , потрібно:

(a / b) * b + a % b = a

Висновок

Згідно з результатами останньої операції, згідно з C99, ознака результату така ж, як і дивідендна.

Давайте подивимось кілька прикладів ( dividend / divisor):

Коли тільки дивіденд від'ємний

(-3 / 2) * 2  +  -3 % 2 = -3

(-3 / 2) * 2 = -2

(-3 % 2) must be -1

Коли тільки дільник від'ємний

(3 / -2) * -2  +  3 % -2 = 3

(3 / -2) * -2 = 2

(3 % -2) must be 1

Коли і дільник, і дивіденд від’ємні

(-3 / -2) * -2  +  -3 % -2 = -3

(-3 / -2) * -2 = -2

(-3 % -2) must be -1

6.5.5 Мультиплікаційні оператори

Синтаксис

  1. мультиплікативний вираз:
    • cast-expression
    • multiplicative-expression * cast-expression
    • multiplicative-expression / cast-expression
    • multiplicative-expression % cast-expression

Обмеження

  1. Кожен з операндів повинен мати арифметичний тип. Операнди оператора % мають цілий тип.

Семантика

  1. Звичайні арифметичні перетворення виконуються на операндах.

  2. Результатом оператора двійкового * є добуток операндів.

  3. Результатом / оператора є коефіцієнт від ділення першого операнда на другий; результат оператора % - залишок. В обох операціях, якщо значення другого операнда дорівнює нулю, поведінка не визначена.

  4. Коли цілі числа діляться, результатом / оператора є алгебраїчний коефіцієнт, який відкидається дробовою частиною [1]. Якщо коефіцієнт a/bє представним, вираз (a/b)*b + a%bповинен дорівнювати a.

[1]: Це часто називають "усіченням до нуля".


1

Оператор модуля дає решту. Оператор модуля в c зазвичай приймає знак чисельника

  1. х = 5% (-3) - тут чисельник позитивний, отже, це призводить до 2
  2. y = (-5)% (3) - тут чисельник від'ємний, отже, це результат -2
  3. z = (-5)% (-3) - тут чисельник від'ємний, отже, це результат -2

Також оператор модуля (залишок) може використовуватися лише з цілим типом і не може бути використаний з плаваючою точкою.


2
Було б непогано, якщо ви можете створити резервну копію за допомогою посилань на зовнішні ресурси.
J ... S

1

Я вважаю, що корисніше мислити так, modяк це визначено в абстрактній арифметиці; не як операція, а як цілий різний клас арифметики, з різними елементами та різними операторами. Це означає, що додавання mod 3не є тим самим, як "нормальне" додавання; це є; ціле додавання.

Отже, коли ви робите:

5 % -3

Ви намагаєтеся зіставити ціле число 5 до елемента в наборі mod -3. Це елементи mod -3:

{ 0, -2, -1 }

Так:

0 => 0, 1 => -2, 2 => -1, 3 => 0, 4 => -2, 5 => -1

Скажіть, вам доведеться чомусь пробути 30 годин, скільки годин у вас залишилось того дня? 30 mod -24.

Але те, що реалізує C, це не modрешта. У будь-якому разі, справа в тому, що має сенс повертати негативи.

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