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

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

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

fmodf(-1,8) // fails similarly

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

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

Ответ 1

Прежде всего, я хотел бы отметить, что вы даже не можете полагаться на то, что (-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;
}

Ссылка: С++ 03, пункт 5.6, раздел 4:

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

Ответ 2

Вот функция 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 == FAIL!

10.2 мод 2.0 = 0.2
10,2 мод -2.0 = -1.8
-10.2 мод 2.0 = 1,8
-10.2 mod -2.0 = -0.2

Ответ 3

Я только что заметил, что Bjarne Stroustrup называет % как оператор остаток, а не оператор modulo.

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

#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 ); }

& hellip; с тем же ограничением на Integer, что это подписанный тип.


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

Ответ 6

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

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

Ответ 7

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

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

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

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

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

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

Обратите внимание, что из C11 и С++ 11 семантика div стала усечением в сторону нуля (см. [expr.mul]/4). Кроме того, для D, деленного на D, С++ 11 гарантирует следующее о quotient 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. С++ 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, есть встроенные операторы для разделения полов. В С++ вам нужно написать функцию, используя приведенные выше определения.

Другим способом является евклидово деление, которое также может быть определено в терминах встроенного усеченного деления

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);

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

Ответ 11

Пример шаблона для С++

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

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

Ответ 12

Просто (х + мод)% мод

Ответ 13

define  MOD(a, b)       ((((a)%(b))+(b))%(b))

Ответ 14

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

Ответ 15

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

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

Ответ 16

Я бы сделал:

((-1)+8) % 8 

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