Мод отрицательного числа тает мой мозг
Я пытаюсь изменить целое число, чтобы получить позицию массива, чтобы он зациклился. дела 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
Я делал это раньше, но почему-то сегодня мой мозг тает:(
15 ответов
Я всегда использую свой собственный 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.
Обратите внимание, что оператор% C# и C++ на самом деле НЕ по модулю, это остаток. Формула для модуля по вашему желанию в вашем случае:
float nfmod(float a,float b)
{
return a - b * floor(a / b);
}
Вы должны перекодировать это в C# (или C++), но это способ, которым вы получаете по модулю, а не остаток.
Однострочная реализация с использованием %
только однажды:
int mod(int k, int n) { return ((k %= n) < 0) ? k+n : k; }
Сравнивая два преобладающих ответа
(x%m + m)%m;
а также
int r = x%m;
return r<0 ? r+m : r;
Никто на самом деле не упомянул тот факт, что первый может бросить OverflowException
в то время как второй не будет. Хуже того, при непроверенном контексте по умолчанию первый ответ может вернуть неправильный ответ (см. mod(int.MaxValue - 1, int.MaxValue)
например). Таким образом, второй ответ не только кажется более быстрым, но и более правильным.
Ответ ShreevatsaR не будет работать во всех случаях, даже если вы добавите "if(m<0) m=-m;", если вы учитываете отрицательные дивиденды / делители.
Например, -12 мод -10 будет 8, и это должно быть -2.
Следующая реализация будет работать как для положительных, так и для отрицательных дивидендов / делителей и соответствует другим реализациям (а именно, Java, Python, Ruby, Scala, Scheme, Javascript и Google's 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 };
}
}
Для более эффективной работы разработчиков
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, посмотрите здесь
Мне нравится трюк, представленный Питером Н. Льюисом в этой теме: "Если n имеет ограниченный диапазон, то вы можете получить желаемый результат, просто добавив известную постоянную, кратную [делителю], которая больше абсолютного значения минимум ".
Так что, если у меня есть значение d в градусах, и я хочу взять
d % 180f
и я хочу избежать проблем, если d отрицательно, то вместо этого я просто делаю это:
(d + 720f) % 180f
Это предполагает, что хотя d может быть отрицательным, известно, что оно никогда не будет более отрицательным, чем -720.
Добавление некоторого понимания.
По евклидову определению мод мод всегда должен быть положительным.
Пример:
int n = 5;
int x = -3;
int mod(int n, int x)
{
return ((n%x)+x)%x;
}
Выход:
-1
Вы ожидаете поведения, которое противоречит документированному поведению оператора% в C# - возможно, потому, что вы ожидаете, что он будет работать так, как он работает на другом языке, к которому вы более привыкли. В документации по С # указано (выделено мной):
Для операндов целочисленных типов результатом a % b является значение, созданное a - (a / b) * b. Знак ненулевого остатка такой же, как и у левого операнда.
Требуемое значение можно рассчитать за один дополнительный шаг:
int GetArrayIndex(int i, int arrayLength){
int mod = i % arrayLength;
return (mod>=0) : mod ? mod + arrayLength;
}
Просто добавьте свой модуль (arrayLength) к отрицательному результату%, и все будет в порядке.
Существует множество реализаций
mod
функция, и я думаю, что стоит перечислить их все --- по крайней мере, согласно Википедии , я уверен, что их больше.
// Important to be able to use `MathF`.
using System;
public static class MathFUtils {
public static class Mod {
public static float Trunc(float a, float b) =>
a - b * ((int)(a / b));
public static float Round(float a, float b) =>
a - b * MathF.Round(a / b);
public static float Floor(float a, float b) =>
a - b * MathF.Floor(a / b);
public static float Ceil(float a, float b) =>
a - b * MathF.Ceiling(a / b);
public static float Euclidean(float a, float b) =>
a - MathF.Abs(b) * MathF.Floor(a / MathF.Abs(b));
}
}
Согласно Википедии (а также моему опыту) придерживаться
Euclidean
. Он наиболее полезен с точки зрения математических и вероятностных свойств. Если вам когда-нибудь понадобится
Trunc
, то я верю
%
делает именно это.
Кроме того, для тех из вас, кто может запутаться в том, что каждый из них делает и как, я настоятельно рекомендую прочитать статью в Википедии (даже если это сложно) и посмотреть изображения каждого представления.
Конечно, они не обязательно самые производительные, но они работают. Если вас беспокоит производительность, я рекомендую найти местного бога C# или спросить его, когда они проходят через наш смертный план.
Все ответы здесь отлично работают, если ваш делитель положительный, но он не совсем полный. Вот моя реализация, которая всегда возвращает диапазон [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
может быть любого типа номера)
Однострочная реализация ответа 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);
Второй ответ ShreevatsaR:
int mod(int x, int m) {
int r = x % m;
return r < 0 ? r + m : r;
}
можно написать с использованием шаблона var и выражений переключения в более новых версиях C# в виде одной строки:
int mod(int x, int m) => (x % m) switch
{
< 0 and var r => r + m, var r => r
}
Вот мой один лайнер для положительных целых чисел, основанный на этом ответе :
Применение:
(-7).Mod(3); // returns 2
реализация:
static int Mod(this int a, int n) => (((a %= n) < 0) ? n : 0) + a;