상세 컨텐츠

본문 제목

부동소수점 - Python round() 함수

개발 Recording/Python

by sm-stack 2023. 7. 7. 00:39

본문

부동소수점과 그에 따른 문제점

파이썬의 round() 함수를 사용하다가 의문점이 들어 공식 Docs를 찾아보았다.

 

Built-in Functions

The Python interpreter has a number of functions and types built into it that are always available. They are listed here in alphabetical order.,,,, Built-in Functions,,, A, abs(), aiter(), all(), a...

docs.python.org

공식 Docs에서 round 함수를 검색하면, 다음과 같은 문구를 볼 수 있다.

round()를 지원하는 빌트인 자료형의 경우, 값들은 10의 -ndigits 제곱의 가장 가까운 배수로 반올림됩니다; 만약 두 배수가 양쪽에 동등하게 떨어져 있다면, 반올림은 짝수 방향(even choice)으로 이루어집니다(예를 들어 round(0.5)와 round(-0.5)는 짝수인 0으로, round(1.5)는 2로 반올림). ndigit에는 모든 정수(양수, 0, 음수)가 들어갈 수 있습니다. 반환값은 ndigit이 생략되거나 None인 경우 정수입니다. 다른 경우, 반환 값은 number와 같은 자료형을 가집니다.
...
참고
float에 대한 round() 의 동작은 예상과 다를 수 있습니다: 예를 들어, round(2.675, 2) 는 2.68 대신에 2.67 을 반환합니다. 이것은 버그가 아닙니다: 대부분의 십진 소수가 float로 정확히 표현될 수 없다는 사실로부터 오는 결과입니다. 자세한 정보는 부동 소수점 산술: 문제점 및 한계를 보세요.

여기서 두 가지 의문점이 생긴다.

1. 반올림이 짝수 방향(even choice)으로 이루어진다는 것은 무엇인가?

2. 대부분의 십진 소수는 float로 정확히 표현될 수 없다는 사실은 무엇인가?

 

이 두 질문에 대해 답해보자.

1. 반올림이 짝수 방향(even choice)으로 이루어진다는 것은 무엇인가?

정확히 반올림하려고 하는 지점이 중간에 있는 상황에서 어디로 반올림할지를 결정하는 법칙을 타이브레이킹 룰이라고 한다. 우리가 일반적으로 알고 있는 타이브레이킹 상황에서의 반올림은 다음과 같다.

x의 소수부가 0.5라고 했을 때, x+0.5로 반올림한다.

그러나 공학 및 금융계에서 주로 사용하는 타이브레이킹 룰은 조금 다르다. round-to-nearest-even 혹은 banker's rule이라고 하는 타이브레이킹 룰의 경우, 5의 앞자리가 홀수인 경우엔 올림을 하고 짝수인 경우엔 버림을 하여 짝수로 만들어준다. 파이썬은 3.x 버전으로 업그레이드되면서 해당 룰에 따라 반올림을 진행한다.

 

2. 대부분의 십진 소수는 float로 정확히 표현될 수 없다는 사실은 무엇인가?

그러나 위 룰은 아래와 같이 적용되지 않는 것처럼 보인다.

>>> round(2.225,2)
2.23
>>> round(2.325,2)
2.33
>>> round(1.325,2)
1.32

>>> round(1.32225, 4)
1.3222
>>> round(2.33225, 4)
2.3323
>>> round(1.33235, 4)
1.3323
>>> round(4.3335,3)
4.333
>>> round(4.3345,3)
4.335

이는 대부분의 십진 소수가 정확히 이진 소수로 표현될 수 없기 때문이다. 예를 들어, 파이썬에서 0.1은 다음과 같이 저장된다.

>>> 0.1
0.1000000000000000055511151231257827021181583404541015625

이에 따라, 아래와 같이 당연해 보이는 식도 False가 나오게 된다.

>>> (0.1 + 0.1 + 0.1) == 0.3
False

이와 같은 문제는 float 자료형을 사용하는 대부분의 언어에서 드러나는 문제점이다. 이에 대해 서술한 글(링크)에서는 en 가지를 해결책으로 제시한다.

  1. double 자료형 사용(python에는 double이 없으므로 해당 X)
  2. 정수형을 먼저 사용한 뒤, 나눗셈을 진행.

 

파이썬에서는 다음과 같이 이러한 오류 가능성을 줄일 수 있다.

  1. float.as_integer_ratio() 메서드 사용 : float의 값을 분수로 표현해주어서, 정확한 값을 계산하기 보다 쉽게 만들어 줌.
  2. float.hex() 메서드 사용 : float에 대해 정확하게 컴퓨터에 저장된 값을 16진수로 반환함. fromhex() 메서드를 통해 다시 정확하게 재구성 가능.

다만 코드의 가독성과 같은 부가적인 문제들을 고려했을 때, 단위를 정수로 표현하고 소수가 들어가는 계산은 제일 마지막 부분에 해야 한다고 생각한다.

 

다른 언어는 어떠한가?

자바스크립트에서는 integer 자료형이 없고, float 자료형만 존재하기에 매우 큰 숫자를 처리하는 데에 있어 어려움을 겪을 수 있다. 특히, Web3에서는 토큰 개수 표현에서 2^53을 넘어가는 숫자들이 종종 등장하기 때문에, 문제가 될 수 있다. 이 때문에 자바스크립트에서는 BigNumber 라이브러리 혹은 내장 객체인 BigInt를 사용한다. 또한, Typescript를 사용하는 등 각 변수의 타입을 명시하려는 노력을 통해 오류의 가능성을 줄여야 한다.

Solidity는 아예 float 자료형을 지원하지 않고, 반올림도 내장 함수가 아니라 써드 파티 라이브러리를 import해서 사용해야 한다. Solidity에서 내장된 메서드는 오직 내림 하나이고, 이 때문에 많은 컨트랙트가 여러 공격 벡터(ex. ERC4626 Inflation Attack)에 영향받고 있다.

 

 

해당 문제는 기본적으로 이진수 표현만이 가능한 컴퓨터의 설계 상 문제라고 생각할 수도 있지만, 파이썬 / 자바스크립트 / 솔리디티 세 가지 언어를 살펴보았을 때 각 언어의 설계에 따라서 발생하는 추가적인 문제들도 존재한다. 특히, 숫자를 정확하게 다뤄야 하는 공학 / 금융권 / 블록체인 영역에 있어서는 각 언어에 따라 이러한 부분을 잘 알아두어야 할 것이다.

'개발 Recording > Python' 카테고리의 다른 글

Block Scope in Python  (0) 2023.07.11
Python으로 힙(Heap) 구현하기  (0) 2022.06.07
Python으로 연결리스트 (LinkedList) 구현하기  (0) 2022.06.07

관련글 더보기