Как закодировать оператор по модулю (%) в C / C ++ / Obj-C, который обрабатывает отрицательные числа


86

Одна из моих любимых ненавистников к языкам, производным от C (как математик), заключается в том, что

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

fmodf(-1,8) // fails similarly

Какое лучшее решение?

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


1
Я не думаю, что это настоящий «дубликат» stackoverflow.com/questions/828092/… в соответствии с официальным определением. Неверно, что ответы на этот вопрос можно объединить с этим, потому что этот вопрос спрашивает только о модуле, а не о делении. Но я думаю, что этот вопрос покрывается этим, так что он близок. Мой ответ уже есть, FWIW.
— Стив Джессоп,

Может быть, эту ветку следует разделить, поскольку она задает два отдельных вопроса. лучший способ сделать это - повторно задать вопрос о разделении отдельно, а затем направить его к этому ответу. Я оставлю это тому, кто лучше разбирается в механизмах этого сайта.
— P i

3
@Pi owhere как %говорят, по модулю ... это остаток .
— obataku 09

1
Вот еще один поток, который является «дубликатом»: stackoverflow.com/questions/1082917/… Просто для справки по этой %проблеме.
— leetNightshade

Если вы делите только силы двух, то, возможно, было бы лучше использовать и:(-1) & 8 == 7
— Хенрикус В.

Ответы:


74

Прежде всего, хочу отметить, что на это нельзя даже рассчитывать (-1) % 8 == -1. единственное, на что можно положиться - это на это (x / y) * y + ( x % y) == x. Однако, является ли остаток отрицательным, определяется реализацией .

Теперь зачем использовать здесь шаблоны? Подойдет перегрузка для int и long.

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 НЕ ПОМОГАЕТ ICE

3
@Armen: Вы удалили сноску <quote> ... целочисленное деление следует правилам, определенным в стандарте ISO Fortran, ISO / IEC 1539: 1991, в котором частное всегда округляется до нуля </quote>. Новый стандарт C ++ модернизирует это поведение с «предпочтительного» до обязательного, точно так же, как Fortran и C.
— Бен Фойгт

2
@Armen: Старая спецификация не работает, но поломка отличается от проблемы со знаком, и ее легко пропустить, пока вы не посмотрите новую формулировку. В C ++ 03 не было «если частное a / b представимо в типе результата», что вызывает проблемы для INT_MIN / -1(в двух реализациях дополнения). В соответствии со старой спецификацией, -32768 % -1возможно, придется оценивать значение -65536(которое также не входит в диапазон 16-битного типа, фу!), Чтобы идентичность сохранялась.
— Бен Фойгт,

1
re «Однако, является ли остаток отрицательным, определяется реализацией.», C ++ 11 гарантирует, что целочисленное деление округляется до 0.
— Ура и hth. - Alf

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 и отдельную функцию для 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 == FAIL!

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: попробуйте (float)1000000001/3, вы будете удивлены результатами!
— cmaster - восстановить

9

Я только что заметил, что Бьярн Страуструп помечает %как оператор остатка , а не оператор по модулю.

Я готов поспорить, что это его формальное название в спецификациях ANSI C и C ++, и что злоупотребление терминологией закралось. Кто-нибудь знает это наверняка?

Но если это так, то функция C fmodf () (и, возможно, другие) вводит в заблуждение. они должны быть помечены как fremf () и т. д.


1
Стандарт C11 (или, если быть точным, окончательный публичный проект ) упоминает «по модулю» шесть раз, но только в отношении представления различных типов. Ни разу не упоминается «по модулю» по отношению к оператору остатка ( %).
— 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.
— Hubert Kario

1
@HubertKario Может быть, еще раз проверим? Невозможно, чтобы что-то по модулю 2000 давало 2295, вы, должно быть, ошиблись.
— Sam Hocevar

2
@SamHocevar: Я думаю, что проблема здесь в странных правилах продвижения целых чисел C.
— преобразование

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

3

Лучшее решение ¹ для математика - использовать Python.

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

Стандартная библиотека 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действительный результат по модулю, даже если он отрицательный. вы можете добавить к нему abs ( b), и он все равно будет действительным результатом по модулю.
— Приветствия и hth. - Alf

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 N (целая степень двойки), для которого по модулю можно вычислить с использованием простой арифметической и побитовой логики как

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 и ссылках в нем.

Обратите внимание, что начиная с C11 и C ++ 11 семантика divстала усечением до нуля (см. [expr.mul]/4). Кроме того, для деления Dна dC ++ 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. %Оператор не имеет ничего общего с классами эквивалентности. Это оператор остатка, а остаток хорошо определен алгебраически как неотрицательный и меньший, чем делитель. К сожалению, C неправильно определил это. Тем не менее, +1 за один из лучших ответов.
— R .. GitHub НЕ ПОМОГАЕТ 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.