음수를 처리하는 C / C ++ / Obj-C에서 모듈로 (%) 연산자를 코딩하는 방법


87

(수학자로서) C 파생 언어를 싫어하는 것 중 하나는

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

fmodf(-1,8) // fails similarly

최상의 솔루션은 무엇입니까?

C ++는 템플릿과 연산자 오버로딩의 가능성을 허용하지만이 두 가지 모두 저에게 어둡습니다. 사례를 감사하게 받았습니다.


1
나는 이것이 공식적인 정의에 따라 stackoverflow.com/questions/828092/…의 꽤 "중복"이라고 생각하지 않습니다 . 이 질문의 답이 그 답으로 병합 될 수 있다는 것은 사실이 아닙니다. 왜냐하면이 질문은 나눗셈이 아니라 계수에 대해서만 묻기 때문입니다. 하지만 저는이 질문이 그 질문에 포함되어 있다고 생각하므로 거의 비슷합니다. 내 대답은 이미 FWIW입니다.
Steve Jessop

두 개의 개별 질문을하기 때문에 해당 스레드를 분할해야 할 수도 있습니다. 이를 수행하는 가장 좋은 방법은 분할 질문을 개별적으로 다시 질문 한 다음 해당 답변을 가리키는 것입니다. 이 웹 사이트의 메커니즘을 더 잘 이해하는 사람에게 맡기겠습니다.
P i

3
@Pi owhere %모듈로 라고합니다 ... 나머지 입니다.
obataku

1
다음은 이것이 "중복"된 또 다른 스레드입니다. stackoverflow.com/questions/1082917/…%문제 에 대한 참조 용 입니다.
leetNightshade 2015

: 만 두의 힘을 분할하는 경우 그것은 더 나은 사용하는 아이디어와 수 있습니다(-1) & 8 == 7
Henricus V.

답변:


75

먼저 (-1) % 8 == -1. 당신이 의지 할 수있는 유일한 것은 (x / y) * y + ( x % y) == x. 그러나 나머지가 음수인지 여부는 구현에 따라 정의됩니다 .

이제 여기서 템플릿을 사용하는 이유는 무엇입니까? int 및 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;
}

참조 : C ++ 03 단락 5.6 조항 4 :

이항 / 연산자는 몫을 산출하고 이항 % 연산자는 첫 번째 표현식을 두 번째로 나눈 나머지를 산출합니다. / 또는 %의 두 번째 피연산자가 0이면 동작이 정의되지 않습니다. 그렇지 않으면 (a / b) * b + a % b는 a와 같습니다. 두 피연산자가 음수가 아니면 나머지는 음수가 아닙니다. 그렇지 않은 경우 나머지 부호는 구현 정의 입니다.


2
@Ohmu : 예, C ++ 표준에 있습니다. <quote> 정수 피연산자의 경우 / 연산자는 분수 부분이 삭제 된 대수 몫을 산출합니다. 몫 a / b가 결과 유형으로 표현 될 수있는 경우 (a / b) * b + a % b는 a와 같습니다. </ quote>
Ben Voigt

5
-1. 구현이 정의 된 지 11 년이 지났습니다. ISO 9899 : 1999는 그것을 정의했고, 안타깝게도 잘못된 정의를 선택했습니다.
R .. GitHub STOP HELPING ICE

3
@Armen : 각주 <quote> ... 정수 나누기는 ISO 포트란 표준, ISO / IEC 1539 : 1991에 정의 된 규칙에 따라 편리하게 삭제했습니다. 여기서 몫은 항상 0으로 반올림됩니다 </ quote>. 새로운 C ++ 표준은 Fortran 및 C와 마찬가지로이 동작을 "선호"에서 필수로 업그레이드합니다.
Ben Voigt

2
@Armen : 이전 사양은 깨졌지만 깨짐은 사인 문제와 다르며 새로운 문구를 볼 때까지 놓치기 쉽습니다. C ++ 03에는 "상수 a / b가 결과 유형으로 표현 가능한 경우"가 없었으며, 이는 INT_MIN / -1(2의 보수 구현에서) 문제를 야기합니다 . 이전 사양에서는 ID를 유지하기 위해 (16 비트 유형의 범위에 있지 않습니다. yuck!) -32768 % -1평가해야 할 수 있습니다 -65536.
Ben Voigt

1
re "그러나 나머지가 음수인지 여부는 구현에 따라 정의됩니다.", C ++ 11은 정수 분할이 0으로 반올림되도록 보장합니다
.

13

다음은 두 연산에 대해 양수 또는 음의 정수 또는 분수 값을 처리하는 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가 아닌 경우에 사용하고 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 == 실패!

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


불행히도 이것은 정수에서는 작동하지 않습니다. 를 사용하려면 나누기 전에 부동 소수점으로 변환해야합니다 floor(). 또한 float로 변환 할 때 정밀도가 떨어질 수 있습니다. Try (float)1000000001/3, 결과에 놀랄 것입니다!
cmaster-monica reinstate monica

9

방금 Bjarne Stroustrup 이 모듈로 연산자가 아닌 나머지 연산자 %로 레이블 을 지정하는 것을 확인했습니다 .

나는 이것이 ANSI C & C ++ 사양의 공식적인 이름이고 용어의 남용이 들어 왔다고 확신합니다. 누구든지 이것을 사실로 알고 있습니까?

그러나 이것이 사실이라면 C의 fmodf () 함수 (그리고 아마도 다른 것)는 매우 오해의 소지가 있습니다. fremf () 등으로 표시되어야합니다.


1
C11 표준 (또는 최종 공개 초안 )은 "모듈로"를 6 번 언급하지만 다양한 유형의 표현과 관련하여 만 언급됩니다. 나머지 연산자 ( %) 와 관련하여 "모듈로"를 한 번도 언급하지 않습니다 .
Nisse Engström

7

양수 모듈로를 찾는 가장 간단한 일반 함수는 다음과 같습니다. x의 양수 값과 음수 값 모두에서 작동합니다.

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

6

정수의 경우 이것은 간단합니다. 그냥 해

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

나는 그것이 N긍정적이고 x. 여러분이 가장 좋아하는 컴파일러는 이것을 최적화 할 수 있어야합니다. 그래서 어셈블러에서 단 하나의 모드 작업으로 끝납니다.


3
작동하지 않습니다에 대한 int x=-9001; unsigned int N=2000;그렇지 않은 999 2295, 제공
휴 버트 Kario

1
@HubertKario 어쩌면 다시 확인? 모듈로 2000이 2295를 제공하는 방법은 없습니다. 실수를 했음에 틀림 없습니다.
sam hocevar 2011

2
@SamHocevar : 여기서 문제는 이상한 C 정수 프로모션 규칙이라고 생각합니다. 서명에 홍보하고 C에 부호를 원용하는 정의되지 않은 동작에 가치 정수 서명 부정적인 홍보에 서명
datenwolf

2
훨씬 더 간단하고 효율적인 형식은 다음과 같습니다 (x < 0) ? (x % N + N) : (x % N).
크리스 Nolet

3

수학자에게 최상의 솔루션 ¹은 Python을 사용하는 것입니다.

C ++ 연산자 오버로딩은 이와 거의 관련이 없습니다. 기본 제공 형식에 대해서는 연산자를 오버로드 할 수 없습니다. 당신이 원하는 것은 단순히 함수입니다. 물론 C ++ 템플릿을 사용하여 단 하나의 코드로 모든 관련 유형에 대해 해당 함수를 구현할 수 있습니다.

표준 C 라이브러리는 fmod부동 소수점 유형에 대해 이름을 올바르게 기억하면를 제공합니다 .

정수의 경우 항상 음이 아닌 나머지 (유클리드 나눗셈에 해당)를 반환하는 C ++ 함수 템플릿을 ...

#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서명 된 유형입니다.


¹ Python의 정수 나눗셈은 음의 무한대로 반올림되기 때문입니다.


귀하의 코드에는 내 편집 이전과 동일한 버그가있는 것 같습니다. b가 음수이면? :)
Armen Tsirunyan

1
@Armen : 감사합니다! 그러나 나는 그것을 편집하기에는 너무 게으르다 ... :-)
건배와 hth. -Alf

@ArmenTsirunyan 다음 r결과를 확인한다 a=이 r + b*(a/b)사실. 정수 나눗셈이 어떻게 구현 되든 상관없이 b*something의 배수입니다 b. 이것은 r음수 일지라도 유효한 모듈로 결과를 만듭니다 . abs ( b)를 추가 할 수 있으며 여전히 유효한 모듈로 결과입니다.
건배와 hth. - 알프

2
@downvoters :이 대답은 여전히 ​​정확하지만 선택한 "솔루션"에는 C ++ 11의 새로운 보장으로 인해 잘못된 설명이 포함되어 있습니다. 여전히 올바른 답변에 반대표를 던지는 것은 매우 아이러니합니다. 아무 이유없이, 거의 절대적인 수준의 무지 함을 가진 적어도 2 명의 연관성이있는 사람이이 질문의 논평을 읽고 무릎을 꿇고 연합 적으로 반대 투표를 받았다고 가정 할 필요가 없습니다. 당신의 반대표를 설명 해주세요.
건배와 hth. - 알프

1
수학적으로 원하는 결과는 나머지가 0이거나 제수 (분모)와 같은 부호를 갖는 것입니다. 제수가 음수이면 나머지는 0이거나 음수 여야합니다. C / C ++ 구현은 나머지가 0이거나 피제수 (분자)와 같은 부호를 갖습니다.
rcgldr

2

아, 이것도 % 디자인이 싫어 ....

다음과 같은 방법으로 배당금을 부호없는 것으로 변환 할 수 있습니다.

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

result = (offset + dividend) % divider

여기서 오프셋은 모듈의 배수 (-INT_MIN)에 가장 가깝기 때문에 더하거나 빼도 모듈로가 변경되지 않습니다. unsigned 유형이 있으며 결과는 정수입니다. 불행히도 INT_MIN ... (-offset-1) 값을 올바르게 변환 할 수 없습니다. 그러나이 방법은 상수 분배기로 작업 할 때 연산 당 하나의 추가 산술 만 (조건부 없음) 장점이 있으므로 DSP와 유사한 응용 프로그램에서 사용할 수 있습니다.

분할기가 2N ( 2의 정수 거듭 제곱) 인 특수한 경우 가 있습니다. 모듈로는 다음과 같이 간단한 산술 및 비트 논리를 사용하여 계산할 수 있습니다.

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를 사용합니다.


2

이 문제에 대한 또 다른 해결책은 int 대신 long 유형의 변수를 사용하는 것입니다.

저는 % 연산자가 음수 값을 반환하는 코드를 작업 중이 었는데, 이로 인해 문제가 발생했습니다 ([0,1]에서 균일 한 랜덤 변수를 생성하는 경우 음수를 원하지 않습니다 :)).하지만 변수를 다음으로 전환 한 후 type long, 모든 것이 원활하게 실행되고 결과는 파이썬에서 동일한 코드를 실행할 때 얻은 결과와 일치했습니다 (여러 플랫폼에서 동일한 "무작위"숫자를 생성 할 수 있기를 원했기 때문에 중요했습니다.


2

다음은이 Microsoft Research 문서 와 그 안의 참조를 기반으로 한 이전 질문에 대한 새로운 답변 입니다.

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멤버가있는 구조체를 반환하는 함수 도 제공합니다.quotrem절단 분할 방법.

가능한 다른 정의가 있습니다. 예를 들어, 소위 플로어 디비전 은 내장 잘린 디비전으로 정의 될 수 있습니다.

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

유클리드 나눗셈 을 사용하면 나머지 부호는 항상 양수 입니다.


assert(signum(rT) == signum(D));확실히 실패 할 수 있습니다. 올바른 설명 : signum(rT)세트 { 0, signum(D)} 의 구성원 이거나 주장으로assert(rT == 0 || signum(rT) == signum(D));
Ben Voigt

@BenVoigt 어설 션을 실행하는 반례를 줄 수 있습니까?
TemplateRex

반례 : D = 10andd = 5
Ben Voigt

답변의 마지막 굵은 글씨도 잘못되었습니다. "긍정적"이 아니라 "음이 아닌"이어야합니다.
Ben Voigt

2

브랜치없이 1 개의 모드 만 사용하는 솔루션의 경우 다음을 수행 할 수 있습니다.

// Works for other sizes too,
// assuming you change 63 to the appropriate value
int64_t mod(int64_t x, int64_t div) {
  return (x % div) + (((x >> 63) ^ (div >> 63)) & div);
}

1
/ * 경고 : 매크로 모드는 인수의 부작용을 여러 번 평가합니다. * /
# mod (r, m) 정의 (((r) % (m)) + ((r) <0)? (m) : 0)

... 또는 동등 클래스의 대표자를 얻는 데 익숙해집니다.


2
"동등 클래스의 대표자를 얻는 데 익숙해 지십시오"?! 말도 안 돼. 원하는 경우 원래 "대표자"를 사용할 수 있습니다 r. %연산자는 등가 클래스와는 아무 상관이있다. 나머지 연산자이고 나머지는 음수가 아니고 제수보다 작도록 대수적으로 잘 정의되어 있습니다. 슬프게도 C는 그것을 잘못된 방식으로 정의했습니다. 그래도 최고의 답변 중 하나가 있으면 +1합니다.
R .. GitHub STOP HELPING ICE

0

C ++ 용 예제 템플릿

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

이 템플릿을 사용하면 반환 된 나머지는 0이거나 제수 (분모)와 동일한 부호 (음의 무한대로 반올림하는 것과 동일)를 갖습니다. 나머지 C ++ 동작은 0이거나 피제수 ( 분자) (0으로 반올림하는 것과 동일).



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

-1

이 솔루션 ( mod양수일 때 사용 )은 음수 나누기 또는 나머지 연산을 모두 함께 사용하지 않습니다.

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

-2

난 그럴거야:

((-1)+8) % 8 

이것은 원하는대로 7을 제공하는 모듈로를 수행하기 전에 첫 번째 숫자에 후자의 숫자를 추가합니다. 이것은 -8까지의 숫자에 대해 작동합니다. -9의 경우 2 * 8을 추가하십시오.


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