이번 포스팅에서는 올림, 내림, 반올림(Ceil, Floor, Round)의 개념과 이를 수행하는 파이썬(Python) 함수를 구현해보자.
1. 반올림(Round)
1) 정의
반올림 과정은 여러 가지로 설명할 수 있겠지만 나는 0 또는 양수인 경우와 음수인 경우 2가지로 나누어 정의했다.
Case 1) 0 또는 양수인 경우
먼저 반올림할 자리수를 정하고 해당 자리를 0으로 바꾸어 준다. 해당 자리에 있던 숫자가 5보다 크거나 같으면 다음 자리에 1을 더하고 그렇지 않다면 내버려 둔다.
예를 들어 양수 2.3을 소수점 첫째 자리에서 올림한다고 해보자. 그러면 올림과정은 아래와 같다.
Case 2) 음수인 경우
음수의 경우에는 Case 1)에서 마지막 과정만 다르다. 즉, 해당 자리에 숫자가 5보다 크면 다음 자리에 1을 빼준다.
이번엔 음수 -2.6을 소수점 첫째자리에서 올림 하면 어떻게 될까?
2) 파이썬 함수
반올림을 수행하는 함수는 원래 내장 함수나 Numpy에서 제공하는 함수를 쓰려고 했으나 아래와 같은 문제점을 발견했다.
예를 들어 265를 1의 자리에서 반올림한다면 270이 나와야 한다. 하지만 결과는?
import numpy as np
print(np.round(265, -1))
print(round(265, -1))
Numpy에서 제공하는 함수나 파이썬 내장 함수인 round가 모두 260을 출력했다. ㄷㄷ; 근데 웃긴 건 255로 하면 260으로 잘 나온다는 것이다.
print(np.round(255, -1))
print(round(255, -1))
이런 일이 왜 발생할까?...
그건 numpy.round와 round 함수가 오사오입 방법으로 반올림하기 때문이다. 오사오입은 반올림할 자리가 5인 경우 그 앞자리가 짝수면 5를 버리고 홀수면 5를 버리고 앞자리에 1을 더하는 방식이다. 그 외의 경우(5미만, 5초과)는 사사오입과 같다. 아래 댓글 달아주신 분께서 알려주셔서 오사오입이라는 것을 알 수 있었다.
이런 결과를 보고 내가 직접 구현해야겠다는 생각이 들었다.
먼저 반올림할 자리를 0으로 바꿔주는 함수를 만들었다. 여기서 digit = 0 은 소수 첫째 자리이며 1은 소수 둘째 자리이다. 만약 -2라면 10의 자리를 뜻한다.
def do_truncate(x, digit=0):
multiplier = 10 ** digit
return int(x * multiplier) / multiplier
그리고 앞에서 반올림하는 과정에 맞춰서 반올림 함수 do_round를 정의했다. 이때 출력 형식은 반올림하는 자리수에 맞춰서 나타내려고 했다. 왜냐하면 부동소수점의 정확도 때문에 정확한 값이 안 나올 때도 있어서다. 예를 들어 34.4가 나와야 되는데 34.40000000000005 이런 식으로 나올 때가 있기 때문에 이를 문자 포맷을 이용하여 정정한 것이다.
def do_round(x, digit):
trunc_x = do_truncate(x, digit+1)
multiplier = 10 ** (digit+1)
temp_x = str(int(x*multiplier))
temp_x = temp_x.replace('-','') ## 해당 자리 숫자를 뽑는다.
if x >=0:
if int(temp_x[-1]) >= 5:
res = do_truncate(x, digit)+10**(-(digit))
else:
res = do_truncate(x, digit)
else:
if int(temp_x[-1]) > 5:
res = do_truncate(x, digit)-10**(-(digit))
else:
res = do_truncate(x, digit)
digit_int = max(0, digit+1)
format_str = f"%.{digit_int}f"
return format_str % res
먼저 Numpy나 Python 내장 함수에서 틀린 결과가 여기서는 잘 작동하는지 살펴보았다.
do_round(265, -1)
오 잘 나왔다. 이제 양수에 대해서 여러 자릿수에 대해서 반올림한 결과를 출력해보자.
x=265.437
text = ['소수 셋째 자리', '소수 둘째 자리', '소수 첫째 자리', '일의 자리', '십의 자리']
for i, digit in enumerate([2, 1, 0, -1, -2]):
print(f'{text[i]} 반올림 {do_round(x, digit)}')
잘 나오는 것을 확인할 수 있다. 이번엔 음수로 해보자.
x=-312.377
text = ['소수 셋째 자리', '소수 둘째 자리', '소수 첫째 자리', '일의 자리', '십의 자리']
for i, digit in enumerate([2, 1, 0, -1, -2]):
print(f'{text[i]} 반올림 {do_round(x, digit)}')
역시 결과가 잘 나온 것을 확인할 수 있다.
2. 올림(Ceil)
1) 정의
Case 1) 0 또는 양수인 경우
올림은 올림 할 자릿수를 정한 뒤 그 자릿수가 0이 아니라면 이를 0으로 바꾸고 바로 다음 윗자리수에 1을 추가한다. 만약 0이 아니라면 그대로 내버려 둔다. 물론 해당 자리보다 더 오른쪽에 있는 수들은 제거해야 한다.
예를 들어 숫자 2.3을 소수점 첫째 자리에서 올림한다고 해보자. 그러면 올림과정은 아래와 같다.
그리고 숫자 2.0을 소수점 첫째자리에서 올림 하면 어떻게 될까?
Case 2) 음수인 경우
음수의 경우의 올림은 올림 할 자릿수를 정한다. 그러고 해당 자리를 0으로 바꾸면 끝이다. 여기서도 해당 자리보다 더 오른쪽에 있는 수들은 없어져야 한다.
예를 들어 -2.3을 소수 첫째 자리에서 올림 한다면 다음과 같다.
2) 파이썬 함수
올림을 수행하는 함수는 다음과 같이 만들 수 있다. 양수인 경우 원래의 수가 올림 하려는 자리 숫자를 0으로 바꾼 숫자(예 2.3인 경우 2.0)보다 항상 크거나 같으며 큰 경우는 해당 자리 숫자는 0이 아니라는 뜻이므로 다음 자리수에 1을 더했다. 그리고 음수인 경우는 원래의 수가 올림하려는 자리 숫자를 0으로 바꾼 수보다 항상 작거나 같게 되어(예 -2.3과 -2.0) 조건식 else에 걸리므로 원래 숫자에서 해당 자리를 항상 0으로 바꾸어 출력하게 된다.
def do_ceil(x, digit=0):
trunc_x = do_truncate(x,digit)
if x > trunc_x:
res = trunc_x+10**(-digit)
else:
res = trunc_x
# 스트링 포맷으로 변경하되 소수가 포함된 경우 올림하려는 자리까지 나타냄.
digit_int = max(0, digit+1)
format_str = f"%.{digit_int}f"
return format_str % res
이제 양수를 가지고 여러 자리로 올림 해보자.
x=265.437
text = ['소수 셋째 자리', '소수 둘째 자리', '소수 첫째 자리', '일의 자리', '십의 자리']
for i, digit in enumerate([2, 1, 0, -1, -2]):
print(f'{text[i]} 올림 {do_ceil(x, digit)}')
정상적으로 나온 것을 확인할 수 있다. 이번엔 음수를 올림 해보자.
x=-312.377
text = ['소수 셋째 자리', '소수 둘째 자리', '소수 첫째 자리', '일의 자리', '십의 자리']
for i, digit in enumerate([2, 1, 0, -1, -2]):
print(f'{text[i]} 올림 {do_ceil(x, digit)}')
올림이 제대로 적용된 것을 알 수 있다.
3. 내림(Floor)
1) 정의
Case 1) 0 또는 양수인 경우
양수의 경우 내림할 자릿수를 정한 뒤 그 자릿수와 (물론 해당 자리보다 더 오른쪽에 있는 자리수는 모두 제거해야 한다) 0으로 바꾸면 끝난다.
예를 들어 숫자 2.3을 소수점 첫째 자리에서 내림한다고 해보자. 그러면 과정은 아래와 같다.
Case 2) 음수인 경우
음수의 경우의 내림할 자릿수를 정한다. 이때 해당 자리 숫자가 0이라면 내버려 두고 아니라면 0으로 바꾸고 나서 다음 자리에 1을 빼준다
예를 들어 -2.6을 소수 첫째 자리에서 내림한다면 다음과 같다.
2) 파이썬 함수
아래 코드는 내림을 수행하는 함수(do_floor)이다. 여기서는 do_truncate를 써서 해당 자리 숫자를 0으로 바꾸는 게 아닌 해당 자리수를 1의 자리로 옮겨주고 이를 int를 취해서 그 오른쪽 자리 숫자를 없앤다음 다시 원위치로 옮기는 방식을 취했다.
def do_floor(x, digit=0):
multiplier = 10 ** digit
if x >= 0:
res = int(x * multiplier) / multiplier
else:
if x == int(x * multiplier)/ multiplier:
res = int(x * multiplier) / multiplier
else:
res = int(x * multiplier) / multiplier-10 ** (-digit)
digit_int = max(0, digit+1)
format_str = f"%.{digit_int}f"
return format_str % res
이번에도 실험을 해보자. 먼저 양수
x=265.437
text = ['소수 셋째 자리', '소수 둘째 자리', '소수 첫째 자리', '일의 자리', '십의 자리']
for i, digit in enumerate([2, 1, 0, -1, -2]):
print(f'{text[i]} 내림 {do_floor(x, digit)}')
이번엔 음수로 해보자.
x=-312.377
text = ['소수 셋째 자리', '소수 둘째 자리', '소수 첫째 자리', '일의 자리', '십의 자리']
for i, digit in enumerate([2, 1, 0, -1, -2]):
print(f'{text[i]} 내림 {do_floor(x, digit)}')
양수, 음수 모두 내림이 잘 적용되었다.
지금까지 반올림, 올림, 내림(Round, Ceil, Floor)의 개념과 파이썬으로 구현하는 방법을 알아보았다. 원래는 단순히 np.round나 round 함수를 소개하는 정도로 가볍게 다루려고 했는데 예상치 못한 에러를 발견하여 내용이 길어졌다. 부디 이 글이 도움이 되었으면 좋겠다.
'프로그래밍 > Python' 카테고리의 다른 글
[Python] iter함수와 itertools를 이용하여 메모리를 절약하면서 배열을 순서대로 그룹화(Grouping)하기 (417) | 2022.04.23 |
---|---|
[Python] 순열(Permutation)과 조합(Combination) 구하기 - itertools (421) | 2022.04.22 |
[Python] 파일 크기(용량, 사이즈) 확인하기 - 메가 바이트(MB), 기가 바이트(GB) 등 여러 단위로 파일 크기 확인하기 (385) | 2022.04.15 |
[Python] 내가 만든 파이썬(Python) 파일(.py) 임포트하기 (414) | 2022.04.05 |
[Python] 모듈 경로(Module Path) 알아보기 (361) | 2022.04.05 |
댓글