음수의 모드는 내 뇌를 녹이고있다


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

나는 전에 이것을했지만 어떤 이유로 든 오늘 내 두뇌가 녹고있다 :(



답변:


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을 추가하면 값 modulo m을 변경하지 않고 양수 범위에 놓입니다.


7
참고 : 완전한 수 이론적 완전성을 위해 맨 위에 "if (m <0) m = -m;"이라는 줄을 추가 할 수 있습니다. 이 경우 "arrayLength"가 항상 긍정적 인 것으로 중요하지는 않습니다.
ShreevatsaR

4
m 값을 확인하려는 경우 0도 제외해야합니다.
billpg

6
@RuudLenders : 제 경우 X = -5이고, m = 2 인 경우, r = x%m이다 -1후, r+m이다 1. while 루프는 필요하지 않습니다. 요점은 (답변에 쓴 것처럼) x%m항상 엄격하게보다 큽니다 . 긍정적으로 만들려면 최대 한 번 -m추가해야합니다 m.
ShreevatsaR

4
@dcastro : 내가 8로 -12 모드를 -10하려는이 대표 따기 경우 것으로, 수학에서 가장 일반적인 관례 r에 대한 a모듈을 b, 다음은입니다 0 ≤ R <| B |.
ShreevatsaR

8
+1. 음수 계수에 대해 개별 언어가하는 일에 신경 쓰지 않습니다. '최소의 음이 아닌 잔류 물'은 수학적 규칙 성을 나타내며 모호성을 제거합니다.
Brett Hale

80

C #과 C ++의 % 연산자는 실제로 모듈로가 아니며 나머지입니다. 원하는 경우 모듈로의 공식은 다음과 같습니다.

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

이것을 C # (또는 C ++)으로 다시 코딩해야하지만 나머지가 아닌 모듈로를 얻는 방법입니다.


21
"C ++의 % 연산자는 실제로 모듈로가 아니며 나머지입니다."고맙습니다. 이제는 말이됩니다. 왜 음수가 제대로 작동하지 않는지 궁금합니다.
leetNightshade

2
"C ++의 % 연산자는 실제로 모듈로가 아니며 나머지입니다."이것이 정확하지 않다고 생각하고 왜 모듈로가 나머지와 다른지 알 수 없습니다. 그것이 Modulo Operation Wikipedia 페이지에도 나와 있습니다. 단지 프로그래밍 언어가 음수를 다르게 취급한다는 것입니다. C #의 모듈로 연산자는 분명히 나머지 "0"부터 계산합니다 (-9 % 4 = -1, 4 * -2는 -1이므로 차이는 -1이므로). 다른 정의에서는 -9 % 4를 +3으로 간주합니다. -4 * 3은 -12이고 나머지는 +3입니다 (예 : Google의 검색 기능에서 백엔드 언어가 확실하지 않음).
타이 레스

18
Tyress는 계수와 나머지에 차이가 있습니다. 예를 들면 : -21 mod 4 is 3 because -21 + 4 x 6 is 3. 그러나 -21 divided by 4 gives -5A를remainder of -1 . 양수 값의 경우 차이가 없습니다. 이러한 차이점에 대해 알려주십시오. 그리고 항상 Wikipedia를 믿지 마라 :)
Петър Петров

2
왜 모듈로 대신 나머지 함수를 사용하고 싶습니까? 그들은 왜 만들 었는가% 나머지 습니까?
Aaron Franke

4
@AaronFranke-이전 하드웨어에서 몫과 나머지를 신속하게 생성하는 이전 cpus의 유산-이것이 하드웨어에 부정적인 배당을 부여한 것입니다. 언어는 단순히 하드웨어를 반영했습니다. 대부분의 시간 동안 프로그래머들은 긍정적 인 배당금으로 일하고 있었고이 문제를 무시했습니다. 속도가 가장 중요했습니다.
ToolmakerSteve

16

%한 번만 사용하는 단일 라인 구현 :

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

1
이 올바른지? 나는 그것을 다른 사람이 받아 들인 것으로 보지 않거나 그것에 대한 어떤 언급도하지 않습니다. 예를 들어. mod (-10,6)은 6을 반환합니다. 맞습니까? 4를 반환하지 않아야합니까?
John Demetriou

3
@JohnDemetriou 귀하의 숫자는 모두 틀 렸습니다 : (A) 2를 반환하고 (B) 2를 반환합니다; 코드를 실행 해보십시오. 항목 (A) : mod(-10, 6)손 으로 찾으 려면 답이 범위 안에 올 때까지 6을 반복적으로 더하거나 빼십시오 [0, 6). 이 표기법은 "왼쪽에 포함하고 오른쪽에 배타적"을 의미합니다. 우리의 경우, 우리는 6 코드는 매우 간단 2를 제공, 두 번 추가하고, 그것이 맞아 것을 쉽게 알 수있다 : 첫째, 그것은 / 추가 공제에 해당하지 n가 하나의 중지 점을 제외하고 위와 n에서 접근하는 경우, 짧은 부정적인 측면. 이 경우 수정합니다. 댓글 :)
Evgeni Sergeev 1

1
그건 그렇고, 여기에 싱글을 사용 %하는 것이 좋은 생각 인 이유 가 있습니다. 표 참조 빠른 관리 코드 작성 : 비용이 얼마인지 알고 문서의 관리 코드 비용 에 나와 있습니다. 사용 %int div표 에 나열된 것과 유사하게 비쌉니다 . 더하기 또는 빼기보다 약 36 배 더 비싸고 곱하는 것보다 약 13 배 비쌉니다. 물론 이것이 코드의 핵심이 아닌 이상 큰 문제는 없습니다.
Evgeni Sergeev

2
그러나 %특히 쉽게 예측할 수없는 경우 테스트 및 점프보다 비용이 많이 듭니까?
Medinoc

6

부정적인 배당 / 제수를 설명하는 경우 "if (m <0) m = -m;"을 추가하더라도 ShreevatsaR의 답변은 모든 경우에 적용되지 않습니다.

예를 들어, -12 mod -10은 8이고 -2 여야합니다.

다음 구현은 양수 및 음수 배당 / 제수 모두에 대해 작동하며 다른 구현 (즉, Java, Python, Ruby, Scala, Scheme, Javascript 및 Google 계산기)을 준수합니다.

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
(... 계속) 둘째, 음의 계수를 위해해야 ​​할 일은 관습의 문제입니다. 예를 들어 Wikipedia를 참조하십시오 . "일반적으로, 수 이론에서, 긍정적 인 나머지는 항상 선택된다", 이것이 내가 그것을 배운 방법이다 (버튼의 기본 수 이론 ). Knuth는 또한 그것을 그렇게 정의합니다 (특히, r = a - b floor(a/b)항상 긍정적입니다). 예를 들어 컴퓨터 시스템 중에서도 파스칼과 메이플은 항상 긍정적으로 정의합니다.
ShreevatsaR

@ShreevatsaR 유클리드 정의에 따르면 결과가 항상 긍정적이라는 사실을 알고 있습니다. 그러나 대부분의 최신 모드 구현은 음수 제수 "n"에 대해 [n + 1, 0] 범위의 값을 반환한다는 인상을 받고 있습니다. 이는 -12 mod -10 = -2를 의미합니다. 필자는 Google Calculator , Python , RubyScala를 조사 했으며 모두이 규칙을 따릅니다.
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?
Jeff B

@JeffBridgman 나는 유클리드 정의에 근거한다고 언급했다. `나머지에 대해서는 두 가지 가능한 선택이 있는데, 하나는 부정적이고 다른 하나는 긍정적이며, 몫에 대해서도 두 가지 가능한 선택이 있습니다. 일반적으로 숫자 이론에서는 the positive remainder is always chosen프로그래밍 언어가 언어 및 a 및 / 또는 n의 부호에 따라 선택됩니다. [5] 표준 파스칼과 Algol68은 마이너스 제수의 경우에도 양수 (또는 0)를 제공하며, C90과 같은 일부 프로그래밍 언어는 n 또는 a 중 하나가 음수
맡깁니다. '

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 대답해야 할 것이다. 구현은 3을 리턴합니다. 두 매개 변수를 모두 변경 uint하고 캐스트를 제거해야합니다.
나는 오래된 스택 오버플로

5
부호없는 산술은 n2의 거듭 제곱 인 경우에만 동등합니다. 이 (uint)k & (n - 1)경우 컴파일러가 논리 연산을 아직 수행하지 않은 경우 대신 논리 연산 ( )을 대신 사용할 수 있습니다 .
j_schultz

2

이 스레드 에서 Peter N Lewis가 제시 한 트릭이 마음에 듭니다. . "n에 범위가 제한되어 있으면 [제수]의 알려진 상수 배수를 절대 값보다 크게 추가하여 원하는 결과를 얻을 수 있습니다. 최저한의."

그래서 내가 d 값을 가지고 있고 가지고 싶습니다.

d % 180f

d 가 음수 이면 문제를 피하고 싶습니다. 대신이 작업을 수행합니다.

(d + 720f) % 180f

d 는 음수 일 수 있지만 -720보다 음수가 아닌 것으로 알려져 있습니다.


2
-1 : 일반적이지 않습니다 (보다 일반적인 솔루션을 제공하는 것이 매우 쉽습니다).
Evgeni Sergeev

4
이것은 실제로 매우 도움이됩니다. 의미있는 범위가 있으면 계산을 단순화 할 수 있습니다. 내 경우에는 math.stackexchange.com/questions/2279751/…
M.kazem Akhgary

정확히, 이것을 dayOfWeek 계산 (알려진 범위 -6 ~ +6)에 사용했으며 두 개를 절약했습니다 %.
NetMage

@EvgeniSergeev +0 : 나를 위해 : OP 질문에 대답하지는 않지만보다 구체적인 상황에서 (하지만 여전히 질문의 맥락에서) 도움이 될 수 있습니다
Erdal G.

1

c #에서 % 연산자의 문서화 된 동작과 반대되는 동작을 기대하고 있습니다. 아마도 더 익숙한 다른 언어로 작동하는 방식으로 작동하기를 기대하기 때문일 수 있습니다. C # 상태에 대한 설명서 (강조 광산) :

정수 유형의 피연산자의 경우 a % b의 결과는 a-(a / b) * b에 의해 생성 된 값입니다. 0이 아닌 나머지의 부호는 왼쪽 피연산자의 부호와 같습니다.

하나의 추가 단계로 원하는 값을 계산할 수 있습니다.

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

1

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

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숫자 유형이 될 수 있음)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.