본문 바로가기
Algorithm/Accepted

[백준][14888][Python] 파이썬에서 음수 나눗셈에 대해, //

by Baley 2023. 1. 12.

https://www.acmicpc.net/problem/14888

 

14888번: 연산자 끼워넣기

첫째 줄에 수의 개수 N(2 ≤ N ≤ 11)가 주어진다. 둘째 줄에는 A1, A2, ..., AN이 주어진다. (1 ≤ Ai ≤ 100) 셋째 줄에는 합이 N-1인 4개의 정수가 주어지는데, 차례대로 덧셈(+)의 개수, 뺄셈(-)의 개수, 

www.acmicpc.net

문제 자체는 평이하며 브루트포스와 백트랙킹을 활용하면 풀 수 있다.

이 포스팅에서는 문제 자체에 대한 풀이는 다루지 않도록 한다.

이 문제를 따로 포스팅하는 이유는 파이썬의 나눗셈 연산에 대한 의문을 해소하기 위해서이다.

위의 문제는 숫자들을 사칙연산을 해야 하는데 더하기, 빼기, 곱하기는 +, -, * 와 같이 익히 알고 있는 연산자를 쓰면 된다. 그러나 언어 별로 나눗셈에서 그 몫과 나머지를 처리하는 방식이 달라 특별한 조건이 달려있다.

 

나눗셈은 정수 나눗셈으로 몫만 취한다. 음수를 양수로 나눌 때는 C++14의 기준을 따른다. 즉, 양수로 바꾼 뒤 몫을 취하고, 그 몫을 음수로 바꾼 것과 같다

 

나눗셈은 정수 나눗셈으로 몫만을 취한다고 한다. 일단 몫만 취한다는 점에서 나눗셈의 몫만 리턴하는 '//' 연산자를 사용하면 된다고 생각하는 사람들이 많을 것이다.

양수를 양수로 나눌 때는 '//' 연산자를 사용할 때는 다른 언어와 파이썬의 연산결과가 다르지 않으나

음수와 양수를 '//' 연산자를 통해 나눌 때는 파이썬과 다른 언어의 결괏값이 다르다.

각 언어가 어떤 나눗셈 규칙을 이용하는지는 아래의 링크에서 확인할 수 있다.

https://en.wikipedia.org/wiki/Modulo_operation


1. 파이썬에서 음수와 양수의 나눗셈과 몫

1) 양수와 양수의 나눗셈, '//' 연산자 사용 결과

print('3/2 :', 3/2)
print('3//2:', 3//2)
print('3%2 :', 3%2)

 

양수 3에서 양수 2를 나누면 나눗셈 결과는 1.5,

몫만 '//'를 구할 경우 1, 실제 나눗셈의 결과값인 1.5에서 '//'의 결괏값은 1.5의 소수점 아래 값을 버린 1이 된다.

나머지만 '%' 연산자로 구하면 1이 나온다.

 

2) 음수와 양수의 나눗셈, '//' 연산자 사용 결과

print('-3/2 :', -3/2)
print('3/-2 :', 3/-2)
print('3//-2:', 3//-2)
print('-3//2:', -3//2)
print('-3%2 :', -3%2)
print('3%-2 :', 3%-2)

음수 -3과 양수 2, 반대로 양수 3과 음수 -2를 나누면 1)의 나눗셈 결과에서 *-1이 된 결과가 나온다. 3/2의 연산값을 음수로 바꾼 것과 같다.

그러나 '//' 연산자를 사용한 결과값은 1)의 경우와 다르다. 흔히 3//-2, -3//2의 결괏값도 3//2의 결괏값인 1에서 *-1이 된 결과가 나올 것이라 생각하기 쉽다.

그러나 결과값은 '-2'가 출력된다. 이 부분 때문에 백준 14888번에서 헤매는 사람이 많을 것이다.

 

2. '//' 연산자의 연산값이 다른 원인

이렇게 파이썬에서 음수와 양수의 '//' 결괏값이 C++을 포함한 다른 언어와 파이썬이 다른 것은 나머지에 대한 처리가 다르기 때문이다. 

음수는 음수의 무한대와 0사이의 값을 가진다.

C++의 경우 -3/2의 결과가 -1.5일 경우 소수점 아래의 수를 '버림'한다. 따라서 '-3//2'의 연산값은 -1이 된다.

결과적으로 -1.5에서 -1이 되어 '//' 연산값은 0에 가까워진다. 

 

그러나 파이썬은 -3/2의 결과이 -1.5인 경우 소수점 아래의 수를 '올림'한다. 따라서 '-3//2'의 연산값은 -2가 된다.

파이썬의 경우 -1.5에서 -2가 되어 음수의 무한대에 더 가까워진다.

 

3. 왜 파이썬은 다른가

'0을 향한 나머지 연산'과 '음의 무한대를 향한 나머지 연산'에서 파이썬 언어 디자이너가 '음의 무한대를 향한 나머지 연산'을 선택했기 때문이다.

그 이유는 파이썬 언어 디자이너인 귀도의 블로그에 설명되어 있다.

 

Why Python's Integer Division Floors

I was asked (again) today to explain why integer division in Python returns the floor of the result instead of truncating towards zero like...

python-history.blogspot.com

http://python-history.blogspot.com/2010/08/why-pythons-integer-division-floors.html

글의 요지만 간략히 소개하다면 '음의 무한대를 향한 나머지 연산'이 날짜 계산에서 더 유리하다고 판단했기 때문이다.

 

예를 들어 설명하자면

대부분의 언어가 1970년 1월 1일 목요일을 기준점으로 이후 몇 초가 지났는지를 계산하여 날짜를 구한다. 1970년 1월 1일은 기준점 0이 된다.

기준점 0으로부터 지난 초 단위 시간(T) / 86400(하루는 86400초)를 하여 1970년 1월 1일부터 며칠이 지났는지 계산해 날짜를 구한다.

1970년 1월 1일 00:00은 기준점 0이 되었을 때 여기서 38초가 지나면  38/86400초를 계산하여 날짜를 구한다. 예시의 경우 38/86400는 1보다 작으므로 아직 1970년 1월 1일 00:00 38초가 된다.

이렇게 어떤 기준점부터 이후로 얼마나 지났는가를 구하면 '0을 향한 나머지 연산'이든 '음의 무한대를 향한 나머지 연산'이든  별로 달라질 것이 없지만

기준점 이전의 날짜를 구한다면 이야기가 달라진다. 기준점 1970년 1월 1일 00:00보다 4시간 전인 1969년 12월 31일 20:00은 기준점 0으로부터 T가 -14400인 시점이다. -14400이 기준점으로 며칠이 지났는지 알기 위해 '//' 연산자를 통해 -14400//86400을 해보면 파이썬의 경우 -1이 도출된다. 그러나 '0을 향한 나머지 연산'을 이용하는 다른 언어의 경우 0이 나온다.

print('-14400/86400  :', -14400/86400)
print('-14400//86400 :', -14400//86400)

따라서 파이썬의 경구 간단한 '//' 연산자를 통해 T가 -14400인 시점은 기준점 1970년 1월 1일에서 -1일인  1969년 12월 31일임을 알 수 있다.

이런 간편함을 위해 파이썬에서는 음수와 양수에서 몫만 구할 경우 '음의 무한대를 향한 나머지 연산'을 택하고 있다.

댓글