나머지 연산자

kueilove의 이미지

나머지 연산자들 자주 쓰시나요?

그럼

10 % 3

10 % -3

-10 % 3

-10 % -3

의 결과는 어떻게 나올까요?

쉽게 생각했는데 아리송 하더라구요

급하게

#include

int main()
{

printf("10 mod 3 = %d\n", 10%3);
printf("10 mod -3 = %d\n", 10%-3);
printf("-10 mod 3 = %d\n", -10%3);
printf("-10 mod -3 = %d\n", -10%-3);

return 0;
}

위와 같이 짜서 확인해봤는데

엑셀 내장 함수 MOD(A,B)와 결과가 틀리더군요

다들 재미 삼아 한번..

palsuet의 이미지

C와 FORTRAN의 경우

 10 %  3 =  1
 10 % -3 =  1
-10 %  3 = -1
-10 % -3 = -1

Python의 경우
 10 %  3 =  1
 10 % -3 = -2
-10 %  3 =  2
-10 % -3 = -1

나누기도 한번 해 봤습니다.
# C, FORTRAN의 경우
 10 /  3 = 3
 10 / -3 = -3
-10 /  3 = -3
-10 / -3 = 3
# Python의 경우
 10 /  3 = 3
 10 / -3 = -4
-10 /  3 = -4
-10 / -3 = 3

그래서 해 봤습니다.
# C, FORTRAN의 경우
-12 % 3 =  0, -12 / 3 = -4
-11 % 3 = -2, -11 / 3 = -3
-10 % 3 = -1, -10 / 3 = -3
 -9 % 3 =  0,  -9 / 3 = -3
 -8 % 3 = -2,  -8 / 3 = -2
 -7 % 3 = -1,  -7 / 3 = -2
 -6 % 3 =  0,  -6 / 3 = -2
 -5 % 3 = -2,  -5 / 3 = -1
 -4 % 3 = -1,  -4 / 3 = -1
 -3 % 3 =  0,  -3 / 3 = -1
 -2 % 3 = -2,  -2 / 3 =  0
 -1 % 3 = -1,  -1 / 3 =  0
  0 % 3 =  0,   0 / 3 =  0
  1 % 3 =  1,   1 / 3 =  0
  2 % 3 =  2,   2 / 3 =  0
  3 % 3 =  0,   3 / 3 =  1
  4 % 3 =  1,   4 / 3 =  1
  5 % 3 =  2,   5 / 3 =  1
  6 % 3 =  0,   6 / 3 =  2
  7 % 3 =  1,   7 / 3 =  2
  8 % 3 =  2,   8 / 3 =  2
  9 % 3 =  0,   9 / 3 =  3
 10 % 3 =  1,  10 / 3 =  3
 11 % 3 =  2,  11 / 3 =  3
 12 % 3 =  0,  12 / 3 =  4
# Python의 경우
-12 % 3 =  0, -12 / 3 = -4
-11 % 3 =  1, -11 / 3 = -4
-10 % 3 =  2, -10 / 3 = -4
 -9 % 3 =  0,  -9 / 3 = -3
 -8 % 3 =  1,  -8 / 3 = -3
 -7 % 3 =  2,  -7 / 3 = -3
 -6 % 3 =  0,  -6 / 3 = -2
 -5 % 3 =  1,  -5 / 3 = -2
 -4 % 3 =  2,  -4 / 3 = -2
 -3 % 3 =  0,  -3 / 3 = -1
 -2 % 3 =  1,  -2 / 3 = -1
 -1 % 3 =  2,  -1 / 3 = -1
  0 % 3 =  0,   0 / 3 =  0
  1 % 3 =  1,   1 / 3 =  0
  2 % 3 =  2,   2 / 3 =  0
  3 % 3 =  0,   3 / 3 =  1
  4 % 3 =  1,   4 / 3 =  1
  5 % 3 =  2,   5 / 3 =  1
  6 % 3 =  0,   6 / 3 =  2
  7 % 3 =  1,   7 / 3 =  2
  8 % 3 =  2,   8 / 3 =  2
  9 % 3 =  0,   9 / 3 =  3
 10 % 3 =  1,  10 / 3 =  3
 11 % 3 =  2,  11 / 3 =  3
 12 % 3 =  0,  12 / 3 =  4

Python처럼 0에서 불연속이 없는게 좋긴 합니다만, 다른 언어랑 호환이 안되면 귀찮죠.
--
feel the gravity

feel the gravity

ohhara의 이미지


C의 경우에는 위와 같은 행동은 compiler dependent입니다. 즉 compiler가 자기 맘대로
해도 됩니다. -_-;

Taeho Oh ( ohhara@postech.edu , ohhara@plus.or.kr ) http://ohhara.sarang.net
Postech ( Pohang University of Science and Technology ) http://www.postech.edu
Digital Media Professionals Inc. http://www.dmprof.com

Taeho Oh ( ohhara@postech.edu ) http://ohhara.sarang.net
Postech ( Pohang University of Science and Technology ) http://www.postech.edu
Alticast Corp. http://www.alticast.com

juneaftn의 이미지

전웅의 이미지

Phython 같은 언어는 호화스럽게 수식 결과의 "엘레강스"함을 따질 수 있는
형편인지 몰라도, C 는 그렇지 못합니다. C 언어는 언어 정의의 "엘리강스"
함보다 하위 호환성과 성능을 더 중요하게 생각합니다 - 하위 호환성
때문에 typeless 언어의 특성인 implicit int 를 21세기를 눈앞에 두고서야
제거할 수 있었습니다.

0 을 기준으로 한 대칭성에 유난히 민감한 사람들의 관점(아래 설명에서
나오지만 C 언어의 선택 역시 어떤 관점에서는 분명 대칭성을 갖습니다)
에서 C 언어의 정수 나눗셈 방식이 "수학적으로" 덜 일관적이라는 사실
(또한, 실질적으로도 덜 유용할 수 있다는 사실)에는 동의할 수 있으나,
뚜렷한 기준없이 단순히 더 "복잡"하다는 이야기에는 동의할 수 없습니다.

어떤 CPU 가 native intruction 을 통해서 나눗셈을 수행했을 때 덜
"엘레강스"한 결과를 낸다면, "엘레강스"한 결과를 내기 위해서 추가적인
검사나 연산을 수행하는 상황을 "덜" 복잡하다고 말할 수 있을까요? 관점에
따라 다른 문제이며, 당연히 C 언어가 중요시하는 가치에 따르면 C 언어의
선택이 덜 "복잡"한 것입니다.

또한, 음의 정수 나눗셈의 반올림 방향 문제는 피제수가 음수인 경우로만
국한되는 것이 아닙니다 - 따라서 negate 와 negative sign 을 이용한 설명
에는 필연적인 한계가 따르게 됩니다. 음수 나눗셈의 반올림 문제는 피제수
나 제수가 음수인 경우 뿐 아니라, 결과가 "양수"가 되는 피제수 및 제수가
모두 음수인 경우에도 적용됩니다. 즉, C90 에서 음의 정수 나눗셈의
반올림 방향은 구현체 의존적이기 때문에

3 / 2 != (-3 / -2)

가 참일 수도 있습니다.

(제 책에도 꾸겨 넣었던 내용이라고 믿고 있습니다만) 정수 나눗셈과 관련
된 3가지 특징으로 보통 아래를 이야기합니다.

1. 몫 * 제수 + 나머지 == 피제수
2. 피제수의 부호가 바뀌면 몫의 부호만 바뀐다 - 몫의 크기는 바뀌지
   않는다.
3. 제수가 양수이면 나머지 >= 0 이고 나머지 < 제수

구현체가 음의 정수 나눗셈에 일관적인 반올림 방향을 적용한다면, 이
3가지 조건 중 2, 3번은 동시에 만족될 수 없습니다.

C 언어(C90)의 경우에는 반올림 방향을 구현체 의존으로 놓으면서 나눗셈
연산에 대한 기본적인 조건인

    "나머지의 절대값 < 제수의 절대값"

만을 만족하며, 양의 정수 나눗셈에 대한 기본적인 조건인

    "제수 > 0 이고 피제수 >= 0 이면, 나머지 >= 0"

만을 만족해주고 있습니다.

따라서 2, 3번 특징 사이의 선택은 구현체에게 고스란히 남겨지게 됩니다.

초기부터 기본적인 의도는 FORTRAN 과의 호환성 유지를 위해 3번을 포기
하는 것이었으나 C90 제정 당시에는 나눗셈 연산자의 반올림 방향에 대한
합의를 얻지 못해 각 구현체가 가장 유리한 행동을 얻을 수 있도록 결정
하는 것으로 마무리되었으며, C99 개정을 진행하면서 충분한 합의를 얻어
FORTRAN 과 동일한 행동으로 선택되었습니다. 이에 대해 퍼키님은 참조된
글의 댓글에서 "벤더들의 입김"이라고 표현했으나, 실제 C 언어는 FORTRAN
과의 호환성을 상당히 중요하게 생각하고 있으며 - 예를 들어,
에서 볼 수 있는 flaoting-point environment 에 대한 정보들은 모두
FORTRAN 에서 따온 것들이며, 지금도 관련 분야에 대한 표준 수정은 모두
FORTRAN 에서 이루어진 작업들을 참조해 이루어지고 있습니다 - 대부분의
구현체에서 더 나은 성능을 얻을 수 있는 방향이기도 합니다.

현재로서는 3번이 아닌 2번 특성 만족이 C 언어의 특성이며 이로 인해
프로그래밍 과정에서 치명적인 문제점을 안게 된 것은 결코 아닙니다. 3번
특성이 유용할 수 있는 상황(예를 들면, 해쉬의 index 계산)이 분명 존재
하지만, 그와 같은 특성이 필요할 경우

key = value % SIZE;
 
if (key < 0)
    key += SIZE;

와 같은 간단한 수식만으로 얼마든지 원하는 행동을 얻어낼 수 있습니다.

실제 C90 시절에도 2's complement 환경에서 negation 에 따른 정수
overflow 문제를 피하기 위해 정수 연산을 모두 음의 정수로 처리할 때에도
간단한 검사와 단순 연산을 통해 구현체 독립적인 행동을 얻어내는 것이
가능했습니다.

다만, C90 에서 음의 정수 나눗셈 결과가 구현체에 의존한다는 사실 자체가
널리 알려지지 않은 것이 문제였으며, C99 에서 널리 사용되던 구현체와의
하위 호환성을 유지하며 행동이 정의된 점은 quiet change 를 최소화
한다는 점에서 매우 바람직하다고 생각합니다.

새 행동은 새 라이브러리 함수, 새 언어 설계에 충분히 적용할 수 있으리라
생각합니다. 그 언어가 C 언어는 아닙니다.

p.s. 코드를 쓸 때조차 >, < 를 써야 하니 불편하네요. --;

--
Jun, Woong (woong at gmail.com)
http://www.woong.org

시지프스의 이미지

옛날 글이긴 하지만 궁금한 것이 있습니다.
2의 보수를 쓰는 시스템에서 toward to -infinity 방식이 toward to zero 방식보다 쉽지 않나요??
C 언어 관점에서 왜 toward to zero가 좋은지 잘 모르겠네요.
하위 호환성이 문제일 수는 있겠으나 성능 문제는 잘 이해가 안 되고 있습니다.

begin{signature}
THIS IS SPARTA!!!!!n.
end{signature}