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


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

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

fmodf(-1,8) // fails similarly

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

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

15 74

15 ответов:

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

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

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

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

Edit: я нашел ошибку в моем коде. Это не будет работать, если 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:

оператор binary / дает частное, а оператор binary % дает остаток от деления первого выражения на второе. Если второй операнд / или % равен нулю, поведение не определено; в противном случае (a/b)*b + a%b равно a. если оба операнда неотрицательны, то остаток неотрицателен; если нет, то знак остатка реализация-определено.

вот функция C, которая обрабатывает положительные или отрицательные целочисленные или дробные значения для обоих операндов

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

это, безусловно, самое элегантное решение с математической точки зрения. Однако я не уверен, что он надежен в обработке целых чисел. Иногда ошибки с плавающей запятой ползут при преобразовании int - > fp - > int.

Я использую этот код для non-int s и отдельную функцию для int.

Примечание: нужно поймать N = 0!

тестер код:

#include <math.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", x);

    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 mod 2.0 = 0.2
10.2 mod -2.0 = -1.8
-10.2 mod 2.0 = 1.8
-10.2 mod -2.0 = -0.2

Я только что заметил, что Bjarne Stroustrup labels % Как остаток оператор не оператора по модулю.

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

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

для целых чисел это просто. Просто сделай

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

где я предполагаю, что N является положительным и представимым в виде x. Ваш любимый компилятор должен быть в состоянии оптимизировать это, так что он заканчивается только в одной операции mod в ассемблере.

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

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

стандартная библиотека C содержит fmod, Если я правильно помню имя, для типов с плавающей запятой.

для целых чисел можно определить Шаблон функции C++, который всегда возвращает неотрицательный остаток (соответствующий евклидову делению) as ...

#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 это подпись тип.


1 потому что целочисленное деление Python округляется до отрицательной бесконечности.

самая простая общая функция для нахождения положительного модуля будет такой- Он будет работать как на положительных, так и на отрицательных значениях x.

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

О, я ненавижу % дизайн для этого тоже....

вы можете конвертировать дивиденды в unsigned так как:

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

result = (offset + dividend) % divider

где смещение ближе всего к (- INT_MIN) кратному модулю, поэтому добавление и вычитание его не изменит по модулю. Обратите внимание, что он имеет беззнаковый тип и результат будет целочисленным. К сожалению, он не может правильно преобразовать значения INT_MIN...(- смещение-1) так как они вызывают арифметическое переполнение. Но этот метод имеет номере только один дополнительный арифметика в деятельность (и никакие conditionals) при работе с постоянн рассекателем, поэтому она годна к употреблению в DSP-подобных применениях.

есть особый случай, где делитель 2N (целочисленная степень двойки), для которой по модулю можно вычислить с помощью простой арифметики и побитовой логики как

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

это очень короткий, но использовать два %-s, которые обычно медленно.

Я считаю, что другое решение этой проблемы будет использовать переменные типа long вместо int.

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

/* Warning: macro mod evaluates its arguments' side effects multiple times. */
#define mod(r,m) (((r) % (m)) + ((r)<0)?(m):0)

... или просто привыкнешь любого представителя класса эквивалентности.

вот новый ответ на старый вопрос, на основании этого Microsoft Research paper и ссылки в нем.

обратите внимание, что начиная с C11 и C++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 (см. это Q & A исходный код).

с усеченными дивизии, знак остатка равен знаку дивиденда 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);

С евклидовым делением,знак остатка всегда положительно.

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

define  MOD(a, b)       ((((a)%(b))+(b))%(b))
unsigned mod(int a, unsigned b) {
    return (a >= 0 ? a % b : b - (-a) % b);
}

такое решение (для использования, когда mod положительно) избегает принимать отрицательные операции деления или остатка все вместе:

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

Я бы сделал:

((-1)+8) % 8 

это добавляет последнее число к первому, прежде чем делать по модулю давая 7 по желанию. Это должно работать для любого числа до -8. Для -9 добавить 2*8.