Як кодувати оператор за модулем (%) в C / C ++ / Obj-C, який обробляє від’ємні числа


86

Один із моїх домашніх улюбленців ненавидить мови, похідні від С (як математика), - це

(-1) % 8 // comes out as -1, and not 7

fmodf(-1,8) // fails similarly

Яке найкраще рішення?

С ++ допускає можливість шаблонів та перевантаження оператора, але обидва вони для мене каламутні. приклади з вдячністю отримані.


1
Я не думаю, що це є цілком «дублікатом» stackoverflow.com/questions/828092/… згідно офіційного визначення. Неправда, що відповіді на це запитання можуть бути об’єднані з відповідними, тому що це запитання стосується лише модуля, а також не поділу. Але я думаю, що це питання висвітлено цим, тому воно близько. Моя відповідь уже є, FWIW.
Steve Jessop

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

3
@Pi owhere як %кажуть, по модулю ... це залишок .
obataku

1
Ось ще одна нитка, що це "дублікат": stackoverflow.com/questions/1082917/ ... Просто для довідки з цієї %проблеми.
leetNightshade

Якщо ви ділите лише сили на двох, то, можливо, було б краще використовувати і:(-1) & 8 == 7
Генрік В.

Відповіді:


74

Перш за все я хотів би відзначити, що ви навіть не можете покладатися на той факт, що (-1) % 8 == -1. єдине, на що ти можеш покластися, - це на це (x / y) * y + ( x % y) == x. Однак, чи є залишок від'ємним чи ні, визначається імплементацією .

А навіщо тут використовувати шаблони? Перевантаження для ints та longs буде корисним.

int mod (int a, int b)
{
   int ret = a % b;
   if(ret < 0)
     ret+=b;
   return ret;
}

і тепер ви можете назвати це як mod (-1,8), і він буде 7.

Редагувати: Я знайшов помилку в своєму коді. Це не спрацює, якщо b від’ємне. Тому я думаю, що це краще:

int mod (int a, int b)
{
   if(b < 0) //you can check for b == 0 separately and do what you want
     return -mod(-a, -b);   
   int ret = a % b;
   if(ret < 0)
     ret+=b;
   return ret;
}

Посилання: C ++ 03, параграф 5.6, пункт 4:

Двійковий / оператор видає частку, а двійковий% оператор - залишок від ділення першого виразу на другий. Якщо другий операнд / або% дорівнює нулю, поведінка не визначена; інакше (a / b) * b + a% b дорівнює a. Якщо обидва операнди невід’ємні, то залишок невід’ємний; якщо ні, знак залишку визначається реалізацією .


2
@Ohmu: Так, це в стандарті C ++. <quote> Для інтегральних операндів оператор / видає алгебраїчний фактор з будь-якою дробовою частиною, що відкидається; якщо фактор a / b репрезентативний у типі результату, (a / b) * b + a% b дорівнює a. </quote>
Бен Войгт

5
-1. Минуло 11 років з того часу, як це було визначено впровадженням. ISO 9899: 1999 визначив це, і, на жаль, вибрав погане визначення.
R .. GitHub STOP HELPING ICE

3
@Armen: Ви зручно видалили виноску <quote> ... цілочисельний поділ дотримується правил, визначених стандартом ISO Fortran, ISO / IEC 1539: 1991, в якому коефіцієнт завжди округлюється до нуля </quote>. Новий стандарт С ++ покращує цю поведінку з "бажаної" до обов'язкової, як Фортран і С.
Бен Войгт,

2
@Armen: Стара специфікація зламана, але зламаність відрізняється від проблеми зі знаками, і її легко пропустити, поки ви не подивитесь на нове формулювання. У C ++ 03 не було "якщо фактор a / b репрезентабельний у типі результату", що викликає проблеми для INT_MIN / -1(на реалізаціях доповнення двох). За старою специфікацією, -32768 % -1можливо, доведеться обчислити значення -65536(яке також не входить до діапазону 16-бітового типу, юк!), Щоб ідентичність містилася.
Ben Voigt

1
re "Однак, незалежно від того, чи є залишок негативним, це визначається реалізацією.", C ++ 11 гарантує, що цілочисельне ділення округлюється до 0.
Вітаємо та hth. - Альф

12

Ось функція C, яка обробляє позитивні АБО від’ємні цілі чи АБО дробові значення для ОБИХ ОПЕРАНСІВ

#include <math.h>
float mod(float a, float N) {return a - N*floor(a/N);} //return in range [0, N)

Це, мабуть, найелегантніше рішення з математичної точки зору. Однак я не впевнений, чи надійно він обробляє цілі числа. Іноді помилки з плаваючою комою закрадаються при перетворенні int -> fp -> int.

Я використовую цей код для не-int s і окрему функцію для int.

ПРИМІТКА: потрібно затримати N = 0!

Код тестера:

#include <math.h>
#include <stdio.h>

float mod(float a, float N)
{
    float ret = a - N * floor (a / N);

    printf("%f.1 mod %f.1 = %f.1 \n", a, N, ret);

    return ret;
}

int main (char* argc, char** argv)
{
    printf ("fmodf(-10.2, 2.0) = %f.1  == FAIL! \n\n", fmodf(-10.2, 2.0));

    float x;
    x = mod(10.2f, 2.0f);
    x = mod(10.2f, -2.0f);
    x = mod(-10.2f, 2.0f);
    x = mod(-10.2f, -2.0f);

    return 0;
}

(Примітка: Ви можете скомпілювати та запустити його прямо з CodePad: http://codepad.org/UOgEqAMA )

Вихід:

fmodf (-10,2, 2,0) = -0,20 == ПОМИЛКА!

10,2 мод 2,0 = 0,2
10,2 мод -2,0 = -1,8
-10,2 мод 2,0 = 1,8
-10,2 мод -2,0 = -0,2


На жаль, це не працює з цілими числами. Їх потрібно було б перетворити в плаваючу крапку перед поділом, щоб дозволити вам використовувати floor(). Крім того, ви можете втратити точність при перетворенні на плаваючу: спробуйте (float)1000000001/3, ви будете здивовані результатами!
cmaster - відновити

9

Я тільки помітив , що Бйорн Страуструп маркує %як залишковий оператор, НЕ оператор по модулю.

Б'юсь об заклад, що це його офіційна назва в специфікаціях ANSI C & C ++, і що зловживання термінологією закралося.

Але якщо це так, тоді функція fmodf () C (і, мабуть, інші) дуже вводить в оману. вони повинні бути позначені як fremf () тощо


1
Стандарт С11 (або остаточний публічний проект, точніше) згадує "модуль" шість разів, але лише стосовно представлення різних типів. Жодного разу не згадується "modulo" стосовно оператора залишку ( %).
Nisse Engström

7

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

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

6

Для цілих чисел це просто. Просто зробіть

(((x < 0) ? ((x % N) + N) : x) % N)

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


3
Не працює: int x=-9001; unsigned int N=2000;це дає 2295, а не 999.
Губерт Каріо

1
@HubertKario Може перевірити ще раз? Немає можливості, щоб щось за модулем 2000 дало 2295, ви, мабуть, помилились.
sam hocevar

2
@SamHocevar: Я думаю, що проблема тут полягає в дивних правилах просування цілочисельного C. піднятий підвищення до непідписаного та просування негативного підписаного цілого числа до непідписаного викликає невизначену поведінку в C.
datenwolf

1
Я вважаю, що набагато простіше (і більш ефективна) форма буде: (x < 0) ? (x % N + N) : (x % N).
Кріс Нолет

3

Найкращим рішенням ¹для математика є використання Python.

Перевантаження оператора C ++ має мало спільного з цим. Ви не можете перевантажити оператори для вбудованих типів. Те, що ви хочете, - це просто функція. Звичайно, ви можете використовувати шаблони C ++, щоб реалізувати цю функцію для всіх відповідних типів лише з 1 шматочком коду.

Стандартна бібліотека C забезпечує fmod, якщо я правильно пам’ятаю назву, для типів з плаваючою комою.

Для цілих чисел ви можете визначити шаблон функції C ++, який завжди повертає невід'ємний залишок (що відповідає поділу Евкліда) як ...

#include <stdlib.h>  // abs

template< class Integer >
auto mod( Integer a, Integer b )
    -> Integer
{
    Integer const r = a%b;
    return (r < 0? r + abs( b ) : r);
}

... і просто пишіть mod(a, b)замість a%b.

Тут типом Integerмає бути цілочисельний тип із підписом.

Якщо ви хочете, щоб загальна математична поведінка, де знак залишку такий самий, як знак дільника, ви можете зробити, наприклад

template< class Integer >
auto floor_div( Integer const a, Integer const b )
    -> Integer
{
    bool const a_is_negative = (a < 0);
    bool const b_is_negative = (b < 0);
    bool const change_sign  = (a_is_negative != b_is_negative);

    Integer const abs_b         = abs( b );
    Integer const abs_a_plus    = abs( a ) + (change_sign? abs_b - 1 : 0);

    Integer const quot = abs_a_plus / abs_b;
    return (change_sign? -quot : quot);
}

template< class Integer >
auto floor_mod( Integer const a, Integer const b )
    -> Integer
{ return a - b*floor_div( a, b ); }

… З тим самим обмеженням Integer, що це підписаний тип.


¹ Оскільки цілочисельний поділ Python округляється до негативної нескінченності.


здається, у вашому коді така ж помилка, як і у мого до мого редагування. Що робити, якщо b від’ємне? :)
Армен Цирунян

1
@Armen: дякую! але мені лінь редагувати саме для цього ... :-)
Вітаю і hth. - Альф

@ArmenTsirunyan: rрезультат повинен зробити a= r + b*(a/b)true. незалежно від того, як реалізовано цілочисельний поділ, є b*somethingкратним b. це робить rдійсний результат за модулем, навіть якщо негативний. ви можете додати bдо нього abs ( ), і це все одно буде дійсним результатом за модулем.
Вітаю і hth. - Альф

2
@downvoters: Ця відповідь все ще правильна, тоді як вибране "рішення" тепер містить неправильні коментарі через нові гарантії в C ++ 11. Дуже диво іронічно, коли голосувати проти правильної відповіді. Без жодної наведеної причини не слід припускати, що принаймні 2 асоціативні особи з майже абсолютною ступенем невігластва читають коментар до цього питання та асоціюються з протилежними голосами. Будь ласка, поясніть свої голоси проти.
Вітаю і hth. - Альф

1
Математично бажаний результат - залишок дорівнювати нулю або мати той самий знак, що й дільник (знаменник). Якщо дільник від’ємний, то залишок повинен бути нульовим або від’ємним. В результаті реалізації C / C ++ залишок дорівнює нулю або має той самий знак, що і дивіденд (чисельник).
rcgldr

2

О, я ненавиджу% дизайн і для цього ....

Ви можете перетворити дивіденди на неподписані таким чином, як:

unsigned int offset = (-INT_MIN) - (-INT_MIN)%divider

result = (offset + dividend) % divider

де зміщення найближче до (-INT_MIN), кратному модулю, тому додавання та віднімання його не змінить модуля. Зверніть увагу, що він має беззнаковий тип, і результат буде цілим числом. На жаль, він не може правильно перетворити значення INT_MIN ... (- offset-1), оскільки вони викликають арифметичне переповнення. Але цей метод має перевагу лише однієї додаткової арифметики за операцію (і без умовних умов) при роботі з постійним дільником, тому він може бути використаний у DSP-подібних додатках.

Існує особливий випадок, коли дільник дорівнює 2 Н (ціла потужність двох), для якого модуль може бути розрахований за допомогою простої арифметики та побітової логіки як

dividend&(divider-1)

наприклад

x mod 2 = x & 1
x mod 4 = x & 3
x mod 8 = x & 7
x mod 16 = x & 15

Більш поширеним і менш хитрим способом є отримання модуля за допомогою цієї функції (працює лише з додатним дільником):

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

Це просто правильний результат, якщо він негативний.

Також ви можете обдурити:

(p% q + q)% q

Він дуже короткий, але використовуйте два%, які зазвичай повільні.


2

Я вважаю, що іншим рішенням цієї проблеми було б використання змінних типу long замість int.

Я просто працював над деяким кодом, де оператор% повертав негативне значення, що спричинило деякі проблеми (для генерації рівномірних випадкових величин на [0,1] ви насправді не хочете негативних чисел :)), але після перемикання змінних на type long, все працювало гладко, а результати збігалися з тими, які я отримував при запуску того самого коду в python (важливо для мене, оскільки я хотів мати можливість генерувати однакові "випадкові" числа на декількох платформах.


2

Ось нова відповідь на старе запитання, засноване на цій статті Microsoft Research та посиланнях на неї.

Зауважте, що починаючи з С11 та С ++ 11, семантика значень divстала усіченням до нуля (див. [expr.mul]/4). Крім того, якщо Dрозділити на d, C ++ 11 гарантує наступне щодо частки qTта залишкуrT

auto const qT = D / d;
auto const rT = D % d;
assert(D == d * qT + rT);
assert(abs(rT) < abs(d));
assert(signum(rT) == signum(D));

де signumвідображається на -1, 0, +1, залежно від того, чи є його аргумент <, ==,> ніж 0 (див. цей Запитання та відповіді для вихідного коду).

При усіченому діленні знак залишку дорівнює знаку дивідендуD , тобто -1 % 8 == -1. C ++ 11 також забезпечує std::divфункцію, яка повертає структуру з членами quotі remвідповідно до усіченого поділу.

Можливі й інші визначення, наприклад, так званий поверхневий поділ можна визначити в термінах вбудованого усіченого відділу

auto const I = signum(rT) == -signum(d) ? 1 : 0;
auto const qF = qT - I;
auto const rF = rT + I * d;
assert(D == d * qF + rF);
assert(abs(rF) < abs(d));
assert(signum(rF) == signum(d));

При розподіленому на підлозі знаку залишку дорівнює знаку дільникаd . У таких мовах, як Haskell та Oberon, є вбудовані оператори для поверхневого поділу. У C ++ вам потрібно буде написати функцію, використовуючи наведені вище визначення.

Ще один спосіб - це евклідове поділ , яке також можна визначити в термінах вбудованого усіченого поділу

auto const I = rT >= 0 ? 0 : (d > 0 ? 1 : -1);
auto const qE = qT - I;
auto const rE = rT + I * d;
assert(D == d * qE + rE);
assert(abs(rE) < abs(d));
assert(signum(rE) != -1);

При евклідовому поділі знак залишку завжди позитивний .


2

Для рішення, яке не використовує гілок і лише 1 мод, ви можете зробити наступне

// Works for other sizes too,
// assuming you change 63 to the appropriate value
int64_t mod(int64_t x, int64_t div) {
  return (x % div) + (((x >> 63) ^ (div >> 63)) & div);
}

1
/ * Попередження: макромод багато разів оцінює побічні ефекти своїх аргументів. * /
#define mod (r, m) (((r)% (m)) + ((r) <0)? (m): 0)

... або просто звикнути отримувати будь-якого представника для класу еквівалентності.


2
«Звикнися отримувати будь-якого представника для класу еквівалентності» ?! Це нісенітниця. Якщо ви хотіли, щоб ви могли просто використати оригінального "представника" r. %Оператор не має нічого спільного з класами еквівалентності. Це оператор залишку, а залишок добре визначений алгебраїчно, щоб він був невід’ємним і меншим за дільник. На жаль, С визначив це неправильно. Все-таки +1 за те, що ви маєте одну з найкращих відповідей.
R .. GitHub STOP HELPING ICE

0

Приклад шаблону для C ++

template< class T >
T mod( T a, T b )
{
    T const r = a%b;
    return ((r!=0)&&((r^b)<0) ? r + b : r);
}

За цим шаблоном повернутий залишок буде нульовим або мати той самий знак, що і дільник (знаменник) (еквівалент округлення до негативної нескінченності), замість того, щоб поведінка залишку C ++ була рівною нулю або мала той самий знак, що і дивіденд ( чисельник) (еквівалент округлення до нуля).




-1

Це рішення (для використання, коли modпозитивне) дозволяє уникнути негативних операцій розділення або залишку всіх разом:

int core_modulus(int val, int mod)
{
    if(val>=0)
        return val % mod;
    else
        return val + mod * ((mod - val - 1)/mod);
}

-2

Я б зробив:

((-1)+8) % 8 

Це додає останнє число до першого, перш ніж робити модуль, даючи 7 за бажанням. Це має працювати для будь-якого числа до -8. Для -9 додайте 2 * 8.


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