Мод від’ємного числа тане в моєму мозку


189

Я намагаюся модифікувати ціле число, щоб отримати позицію масиву, щоб він замикався. У i % arrayLengthнормальних цифр добре працює, але для від'ємних чисел все йде не так.

 4 % 3 == 1
 3 % 3 == 0
 2 % 3 == 2
 1 % 3 == 1
 0 % 3 == 0
-1 % 3 == -1
-2 % 3 == -2
-3 % 3 == 0
-4 % 3 == -1

тому мені потрібна реалізація

int GetArrayIndex(int i, int arrayLength)

такий, що

GetArrayIndex( 4, 3) == 1
GetArrayIndex( 3, 3) == 0
GetArrayIndex( 2, 3) == 2
GetArrayIndex( 1, 3) == 1
GetArrayIndex( 0, 3) == 0
GetArrayIndex(-1, 3) == 2
GetArrayIndex(-2, 3) == 1
GetArrayIndex(-3, 3) == 0
GetArrayIndex(-4, 3) == 2

Я робив це раніше, але чомусь це тане мозок сьогодні :(


Дивіться дискусію про математичний модуль на math.stackexchange.com/questions/519845/…
PPC

Відповіді:


281

Я завжди використовую власну modфункцію, визначену як

int mod(int x, int m) {
    return (x%m + m)%m;
}

Звичайно, якщо вас турбує два виклики до операції з модулем, ви можете записати це як

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

або його варіанти.

Причина, по якій це працює, полягає в тому, що "x% m" завжди знаходиться в діапазоні [-m + 1, m-1]. Тож якщо він взагалі від'ємний, додавання m до нього поставить його в позитивний діапазон, не змінюючи значення його модуля m.


7
Примітка: для повної теоретичної повноти число ви можете додати рядок у верхній частині, що говорить "if (m <0) m = -m;" хоча в цьому випадку не має значення, оскільки "arrayLength", імовірно, завжди позитивний.
ShreevatsaR

4
Якщо ви збираєтеся перевірити значення m, ви також повинні виключити нуль.
billpg

6
@RuudLenders: Ні. Якщо x = -5 і m = 2, то r = x%mє -1, після чого r+mє 1. Цикл "time" не потрібен. Справа в тому, що (як я писав у відповіді) x%mзавжди суворо більше -m, тому вам потрібно додати щонайменше mодин раз, щоб зробити це позитивним.
ShreevatsaR

4
@dcastro: Я дійсно хочу -12 -10 мод , щоб бути 8. Це найбільш загальне угоду в математиці, що якщо вибирати представника rпо aмодулю b, то таке , що 0 ≤ г <| B |.
ShreevatsaR

8
+1. Мені не байдуже, що робить будь-яка окрема мова для негативного модуля - "найменший негативний залишок" виявляє математичну закономірність і усуває будь-яку неоднозначність.
Бретт Хейл

80

Зверніть увагу, що оператор% C # і C ++ насправді НЕ є модулем, це залишок. Формула модуля, яку ви хочете, у вашому випадку така:

float nfmod(float a,float b)
{
    return a - b * floor(a / b);
}

Ви повинні перекодувати це в C # (або C ++), але це спосіб отримати модуль, а не залишок.


21
"Зверніть увагу, що оператор% C ++ насправді НЕ модуль, це залишок". Дякую, зараз має сенс, завжди дивуйся, чому він ніколи не працював належним чином з від'ємними числами.
leetNightshade

2
"Зверніть увагу, що оператор% C ++ насправді НЕ модуль, це залишок". Я не думаю, що це точно, і я не бачу, чому модуль відрізняється від залишку. Про це йдеться і на сторінці Вікіпедія Modulo Operation. Просто мови програмування по-різному ставляться до негативних чисел. Оператор модуля в C #, очевидно, рахує залишки "від" нуля (-9% 4 = -1, тому що 4 * -2 - -8 з різницею -1), а інше визначення вважатиме -9% 4 як +3, оскільки -4 * 3 - -12, решта +3 (наприклад, у пошуковій функції Google, не впевнений у тому, який там мова є бек-ендом).
Тиріс

18
Тиріс, є різниця між модулем і залишком. Наприклад: -21 mod 4 is 3 because -21 + 4 x 6 is 3. Але -21 divided by 4 gives -5з a remainder of -1. Для позитивних значень різниці немає. Тому, будь ласка, повідомте себе про ці відмінності. І не довіряйте Вікіпедії весь час :)
Петър Петров

2
Чому хтось хоче використовувати функцію залишку замість модуля? Чому вони зробили %залишок?
Аарон Франке

4
@AaronFranke - його спадщина від попереднього процесора, який мав апаратне забезпечення для швидкого виробництва коефіцієнта та залишку - і це те, що апаратне забезпечення дало негативний дивіденд. Мова просто відображає обладнання. Більшість часу програмісти працювали з позитивними дивідендами та ігнорували цю химерність. Швидкість була першорядною.
ToolmakerSteve

16

Однорядкова реалізація, використовуючи %лише один раз:

int mod(int k, int n) {  return ((k %= n) < 0) ? k+n : k;  }

1
це правильно? тому що я не вважаю це прийнятим ким-небудь, ані будь-які коментарі до цього. Наприклад. mod (-10,6) повернеться 6. Це правильно? не повинно повертатись 4?
Джон Деметріу

3
@JohnDemetriou Ваші номери обидва неправильні: (A) він повинен повернути 2 і (B) він повертає 2; спробуйте запустити код. Пункт (A): щоб знайти mod(-10, 6)вручну, ви або додаєте, або віднімаєте 6 повторно, поки відповідь не буде в діапазоні [0, 6). Це позначення означає "включно зліва та виключно справа". У нашому випадку ми додаємо 6 разів двічі, даючи 2. Код досить простий, і легко зрозуміти, що це правильно: по-перше, він робить еквівалент додавання / віднімання, nяк зазначено вище, за винятком того, що він зупиняється на одному nкороткому, якщо наближається з негативна сторона. У цьому випадку ми це виправляємо. Там: коментарі :)
Євгеній Сергєєв

1
До речі, ось причина, чому використання синглу %може бути хорошою ідеєю. Дивіться таблицю Скільки коштують керовані речі в статті Написання швидшого керованого коду: Знайте, що коштує . Використання %аналогічно дорого, ніж int divзазначено в таблиці: приблизно в 36 разів дорожче додавання чи віднімання та приблизно в 13 разів дорожче, ніж множення. Звичайно, нічого страшного, якщо це не є основою того, що робить ваш код.
Євгеній Сергєєв

2
Але чи є сингл %дорожчим за тест і стрибок, особливо якщо його не можна легко передбачити?
Medinoc

6

Відповідь ShreevatsaR не буде працювати у всіх випадках, навіть якщо ви додасте "if (m <0) m = -m;", якщо ви враховуєте негативні дивіденди / дільники.

Наприклад, -12 мод -10 буде 8, а він повинен бути -2.

Наступна реалізація буде працювати як для позитивних, так і для негативних дивідендів / подільників і відповідає іншим реалізаціям (а саме Java, Python, Ruby, Scala, Scheme, Javascript та Google Calculator):

internal static class IntExtensions
{
    internal static int Mod(this int a, int n)
    {
        if (n == 0)
            throw new ArgumentOutOfRangeException("n", "(a mod 0) is undefined.");

        //puts a in the [-n+1, n-1] range using the remainder operator
        int remainder = a%n;

        //if the remainder is less than zero, add n to put it in the [0, n-1] range if n is positive
        //if the remainder is greater than zero, add n to put it in the [n-1, 0] range if n is negative
        if ((n > 0 && remainder < 0) ||
            (n < 0 && remainder > 0))
            return remainder + n;
        return remainder;
    }
}

Тестовий набір за допомогою xUnit:

    [Theory]
    [PropertyData("GetTestData")]
    public void Mod_ReturnsCorrectModulo(int dividend, int divisor, int expectedMod)
    {
        Assert.Equal(expectedMod, dividend.Mod(divisor));
    }

    [Fact]
    public void Mod_ThrowsException_IfDivisorIsZero()
    {
        Assert.Throws<ArgumentOutOfRangeException>(() => 1.Mod(0));
    }

    public static IEnumerable<object[]> GetTestData
    {
        get
        {
            yield return new object[] {1, 1, 0};
            yield return new object[] {0, 1, 0};
            yield return new object[] {2, 10, 2};
            yield return new object[] {12, 10, 2};
            yield return new object[] {22, 10, 2};
            yield return new object[] {-2, 10, 8};
            yield return new object[] {-12, 10, 8};
            yield return new object[] {-22, 10, 8};
            yield return new object[] { 2, -10, -8 };
            yield return new object[] { 12, -10, -8 };
            yield return new object[] { 22, -10, -8 };
            yield return new object[] { -2, -10, -2 };
            yield return new object[] { -12, -10, -2 };
            yield return new object[] { -22, -10, -2 };
        }
    }

По-перше, modфункція зазвичай викликається позитивним модулем (зверніть увагу на змінну arrayLengthв початковому запитанні, на яку тут відповіли, імовірно, ніколи не є негативною), тому функцію насправді не потрібно змушувати працювати для негативного модуля. (Ось чому я згадую про лікування негативного модуля в коментарі до своєї відповіді, а не у самій відповіді.) (Продовження ...)
ShreevatsaR

3
(... продовження) По-друге, що робити для негативного модуля - це питання конвенції. Див., Наприклад, Вікіпедія . "Зазвичай в теорії чисел завжди вибирають позитивний залишок", і саме так я дізнався це (в теорії елементарних чисел Бертона ). Кнут також визначає це так (конкретно, r = a - b floor(a/b)завжди позитивно). Навіть серед комп'ютерних систем, наприклад, Паскаль і Клен, визначають, що це завжди позитивно.
ShreevatsaR

@ShreevatsaR Я знаю, що визначення Евкліда визначає, що результат завжди буде позитивним, але я маю враження, що більшість сучасних реалізацій мод поверне значення в діапазоні [n + 1, 0] для від'ємного дільника "n", що означає, що -12 мод -10 = -2. Я переглянув Google Calculator , Python , Ruby і Scala , і всі вони дотримуються цієї конвенції.
dcastro

Також додати до списку: Scheme and Javascript
dcastro

1
Знову ж таки, це все-таки добре прочитане. Визначення "завжди позитивне" (моя відповідь) відповідає ALGOL, Dart, Maple, Pascal, Z3 і т. Д. "Знак дільника" (ця відповідь) узгоджується з: APL, COBOL, J, Lua, Mathematica, MS Excel, Perl, Python, R, Ruby, Tcl тощо. Обидва не відповідають "знаку дивіденду", як у: AWK, bash, bc, C99, C ++ 11, C #, D, Eiffel, Erlang, Go, Java , OCaml, PHP, Rust, Scala, Swift, VB, x86 збори тощо. Я дійсно не бачу, як можна стверджувати, що одна конвенція є "правильною", а іншою - "неправильною".
ShreevatsaR

6

Додайте трохи розуміння.

За евклідовим визначенням результат моди повинен бути завжди позитивним.

Наприклад:

 int n = 5;
 int x = -3;

 int mod(int n, int x)
 {
     return ((n%x)+x)%x;
 }

Вихід:

 -1

15
Я розгублений ... ви кажете, що результат повинен бути завжди позитивним, але тоді перелічіть результат як -1?
Джефф Б

@JeffBridgman Я це заявив на основі евклідового визначення. `Є два можливі варіанти залишку, один негативний та інший позитивний, а також існує два можливі варіанти коефіцієнта. Зазвичай в теорії чисел, the positive remainder is always chosenале мови програмування вибирають залежно від мови та ознак a та / або n. [5] Стандартні Паскаль і Algol68 дають позитивний залишок (або 0) навіть для негативних дільників, а деякі мови програмування, такі як C90, залишають його до реалізації, коли будь-який з n або a є негативним. "
Abin

5

Порівнюючи дві переважні відповіді

(x%m + m)%m;

і

int r = x%m;
return r<0 ? r+m : r;

Ніхто насправді не згадував про те, що перший може кинути деякий OverflowExceptionчас, а другий не буде. Ще гірше, якщо за умовчанням не перевірено контекст, перша відповідь може повернути неправильну відповідь (див. mod(int.MaxValue - 1, int.MaxValue)Приклад). Тож друга відповідь не тільки здається швидшою, але й правильнішою.


4

Просто додайте свій модуль (arrayLength) до негативного результату%, і ви будете добре.


4

Для більшої продуктивності обізнані розробники

uint wrap(int k, int n) ((uint)k)%n

Невелике порівняння продуктивності

Modulo: 00:00:07.2661827 ((n%x)+x)%x)
Cast:   00:00:03.2202334 ((uint)k)%n
If:     00:00:13.5378989 ((k %= n) < 0) ? k+n : k

Що стосується вартості продуктивності відливання для uint, подивіться тут


3
Способи, які -3 % 10мають бути або -3, або 7. Оскільки потрібен негативний результат, 7 буде відповіддю. Ваша реалізація повертається 3. Вам слід змінити обидва параметри uintта видалити групу.
Мені сподобався старий стек-переповнення

5
Непідписана арифметика є еквівалентною лише тоді, коли nце сила двох, і в цьому випадку ви можете просто використовувати логічний і ( (uint)k & (n - 1)) натомість, якщо компілятор цього ще не зробив для вас (компілятори часто досить розумні, щоб це зрозуміти).
j_schultz

2

Мені подобається трюк, який на цій темі представив Пітер Н Льюїс : "Якщо n має обмежений діапазон, то ви можете отримати результат, який ви хочете, просто додавши відомий постійний кратний [дільник], що перевищує абсолютне значення мінімум ".

Тож якщо у мене є значення d, яке є в градусах, і я хочу прийняти

d % 180f

і я хочу уникнути проблем, якщо d негативний, то натомість я просто роблю це:

(d + 720f) % 180f

Це передбачає, що хоча d може бути негативним, відомо, що воно ніколи не буде більш негативним, ніж -720.


2
-1: недостатньо загальне, (і дати більш загальне рішення дуже просто).
Євгеній Сергєєв

4
Це насправді дуже корисно. коли у вас є значущий діапазон, це може спростити обчислення. у моєму випадку math.stackexchange.com/questions/2279751/…
М.казем Ахгарі

Точно щойно використали це для обчислення dayOfWeek (відомий діапазон від -6 до +6), і це врятувало два %.
NetMage

@EvgeniSergeev +0 для мене: не відповідає на питання ОП, але може бути корисним у більш конкретному контексті (але все ж у контексті питання)
Ердаль Г.

1

Ви очікуєте поведінки, яка суперечить задокументованій поведінці% оператора в c # - можливо, тому, що ви очікуєте, що вона працює так, як вона працює іншою мовою, до якої ви звичні. Документація на C # держав (курсив мій):

Для операндів цілих типів результат% b - це значення, отримане a - (a / b) * b. Знак ненульового залишку такий же, як у лівого операнда

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

int GetArrayIndex(int i, int arrayLength){
    int mod = i % arrayLength;
    return (mod>=0) : mod ? mod + arrayLength;
}

0

Усі відповіді тут чудово працюють, якщо дільник позитивний, але це не зовсім повно. Ось моя реалізація, яка завжди повертається на діапазон [0, b), такий, що знак виходу є таким же, як знак дільника, що дозволяє негативним дільникам як кінцевій точці для вихідного діапазону.

PosMod(5, 3)віддача 2
PosMod(-5, 3)повертає 1
PosMod(5, -3)повертає -1
PosMod(-5, -3)прибутки-2

    /// <summary>
    /// Performs a canonical Modulus operation, where the output is on the range [0, b).
    /// </summary>
    public static real_t PosMod(real_t a, real_t b)
    {
        real_t c = a % b;
        if ((c < 0 && b > 0) || (c > 0 && b < 0)) 
        {
            c += b;
        }
        return c;
    }

(де real_tможе бути будь-який тип номера)


0

Однорядкова реалізація відповіді dcastro (найбільш сумісна з іншими мовами):

int Mod(int a, int n)
{
    return (((a %= n) < 0) && n > 0) || (a > 0 && n < 0) ? a + n : a;
}

Якщо ви хочете продовжувати використовувати %оператор (ви не можете перевантажувати нативні оператори в C #):

public class IntM
{
    private int _value;

    private IntM(int value)
    {
        _value = value;
    }

    private static int Mod(int a, int n)
    {
        return (((a %= n) < 0) && n > 0) || (a > 0 && n < 0) ? a + n : a;
    }

    public static implicit operator int(IntM i) => i._value;
    public static implicit operator IntM(int i) => new IntM(i);
    public static int operator %(IntM a, int n) => Mod(a, n);
    public static int operator %(int a, IntM n) => Mod(a, n);
}

Використовуйте регістр обох робіт:

int r = (IntM)a % n;

// Or
int r = a % n(IntM);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.