많은 프로그래밍 언어가 나머지 연산자를 제공합니다. 하지만 언어마다 결과가 다를 때가 있습니다. C언어, Java, Python, Javascript 네 가지 언어에 대해 나머지 연산자를 비교해 봅시다.
모듈러
위키 백과를 보면 `모듈러 산술(Modular Arithmetic)`를 다음과 같이 정의합니다.
💡 모듈러 산술 혹은 합동 산술은 정수의 합과 곱을 어떤 주어진 수의 나머지에 대하여 정의하는 방법이다.
임의의 정수 $a, b$가 있을 때, 정수 $n$에 대해 $a=b+kn$을 만족시키는 정수 $k$가 존재한다면 $a$와 $b$는 같은 동치류에 속하고 $n$에 대해 합동이라고 합니다. 기호로는 다음과 같이 씁니다. $a\equiv b\space(\text{mod}\space n)$
$n=3$일 때 11과 5를 보면 $11=5+2\times 3$이므로 11과 5는 3에 대해 합동입니다.
언어에서 나머지 연산자
모듈러를 "11은 3으로 나눴을 때 몫이 2이고 나머지가 5이다."라고 이해하곤 합니다. 이 글을 작성하게 된 이유는 음수가 등장하면서 헷갈리기 때문입니다. 3에 대해 -11과 합동인 정수들을 몇 개 찾아봅시다.
$$\begin{align*}-11&=(-3)\times3+(-2)&&\equiv-2\space(\text{mod }3)\\-11&=(-4)\times3+1&&\equiv1\space(\text{mod }3)\end{align*}$$
-11은 3으로 나눴을 때 몫이 -3, 나머지가 2일까요? 몫이 -4, 나머지가 1일까요? 더 많은 몫-나머지 쌍이 있지만 우리가 나머지를 이야기할 땐 절댓값이 작은 모듈러 값을 얘기하기 때문에 양수 중 가장 작은 1과 음수 중 가장 큰 수 -2를 가져왔습니다. 프로그래밍 언어마다 채택의 기준이 달라 정리하고자 합니다. 제수와 피제수 양수 음수에 대해 4가지 경우에 대해 살펴봅시다.
- C
// 11 ➗ 3 → 몫: 3, 나머지: 2
printf("11 ➗ 3 → 몫: %d, 나머지: %d\n",11/3, 11%3);
// -11 ➗ 3 → 몫: -3, 나머지: -2
printf("-11 ➗ 3 → 몫: %d, 나머지: %d\n",-11/3, -11%3);
// 11 ➗ -3 → 몫: -3, 나머지: 2
printf("11 ➗ -3 → 몫: %d, 나머지: %d\n",11/-3, 11%-3);
// -11 ➗ -3 → 몫: 3, 나머지: -2
printf("-11 ➗ -3 → 몫: %d, 나머지: %d\n",-11/-3, -11%-3);
- Java
// 11 ➗ 3 → 몫: 3, 나머지: 2
System.out.printf("11 ➗ 3 → 몫: %d, 나머지: %d\n",11/3, 11%3);
// -11 ➗ 3 → 몫: -3, 나머지: -2
System.out.printf("-11 ➗ 3 → 몫: %d, 나머지: %d\n",-11/3, -11%3);
// 11 ➗ -3 → 몫: -3, 나머지: 2
System.out.printf("11 ➗ -3 → 몫: %d, 나머지: %d\n",11/-3, 11%-3);
// -11 ➗ -3 → 몫: 3, 나머지: -2
System.out.printf("-11 ➗ -3 → 몫: %d, 나머지: %d\n",-11/-3, -11%-3);
- Javascript
Javascript에는 몫 연산자가 없어 나머지만 계산했습니다. `피제수 = 제수 x 몫 + 나머지` 식을 만족시키도록 parseInt 값을 몫으로 사용했습니다.
// 11 ➗ 3 → 몫: 3, 나머지: 2
console.log(`11 ➗ 3 → 몫: ${parseInt(11/3)}, 나머지: ${11%3}`);
// -11 ➗ 3 → 몫: -3, 나머지: -2
console.log(`-11 ➗ 3 → 몫: ${parseInt(-11/3)}, 나머지: ${-11%3}`);
// 11 ➗ -3 → 몫: -3, 나머지: 2
console.log(`11 ➗ -3 → 몫: ${parseInt(11/-3)}, 나머지: ${11%-3}`);
// -11 ➗ -3 → 몫: 3, 나머지: -2
console.log(`-11 ➗ -3 → 몫: ${parseInt(-11/-3)}, 나머지: ${-11%-3}`);
- Python
# 11 ➗ 3 → 몫: 3, 나머지: 2
print(f'11 ➗ 3 → 몫: {11//3}, 나머지: {11%3}')
# -11 ➗ 3 → 몫: -4, 나머지: 1
print(f'-11 ➗ 3 → 몫: {-11//3}, 나머지: {-11%3}')
# 11 ➗ -3 → 몫: -4, 나머지: -1
print(f'11 ➗ -3 → 몫: {11//-3}, 나머지: {11%-3}')
# -11 ➗ -3 → 몫: 3, 나머지: -2
print(f'-11 ➗ -3 → 몫: {-11//-3}, 나머지: {-11%-3}')
- 정리
Python을 제외한 C, Java, Javascript는 결과가 같고 Python만 다릅니다. 편의를 위해 C, Java, Javascript를 C계열이라 부르겠습니다. 이 글의 주제인 몫과 나머지에서는 C과 같은 계열이니 이해부탁드립니다.
C와 Java는 Integer 자료형의 나눗셈은 결과 또한 Integer 자료형입니다. 이 과정에서 절삭으로 계산됩니다. 나누기 참값이 3.x는 모두 3으로 계산되고 -3.x 는 모두 -3으로 계산됩니다. Javascript의 parseInt도 소수점 아래는 절삭하죠. 따라서 양수는 내림, 음수는 올림 되고 절댓값이 작아지는 방향으로 몫이 계산됩니다.
하지만 Python은 절삭이 아닌 내림으로 몫을 계산합니다. 3.x는 모두 3으로 C계열과 같지만 -3.x(-3 미만)는 모두 -4로 계산됩니다.
Python과 C계열의 공통점은 나머지(reminder)의 부호는 제수(divisor)의 부호와 같습니다. 또,`피제수 = 제수 X 몫 + 나머지`를 만족합니다.
- C계열: 소수부를 절삭하고 나머지의 부호는 제수의 부호와 같다.
- Python: 몫을 내림으로 계산하고 나머지의 부호는 제수의 부호와 같다.
'Computer Science > etc.' 카테고리의 다른 글
[Linux] 커널 패치로 TCP 성능 40% 향상시키기(by Google) (0) | 2024.03.27 |
---|---|
의존성 주입(Dependency Injection) (0) | 2023.08.08 |
[Design Pattern] 싱글톤 패턴(Singleton Pattern) (0) | 2023.08.02 |