Як кодувати оператор за модулем (%) в 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.