round - 실수를 정수로 근사하기

지난번 글에서 round라는 것은 반올림이 아니고 근삿값을 구하는 방법이기에 다양한 방법이 있다고 설명했었다. 하지만 프로그래밍에서 round 함수는 대부분 실수를 정수로 만드는 round이다. 그래서 이번 글에서는 실수를 정수로 근사하는 방법들을 설명하도록 하겠다.

우선 실수를 정수로 만드는 근사법 중에서 가장 유명한 것은 ceilfloor이다. 이는 각각 round upround down이라고 부르기도 하는데 이를 번역하여 한국어로는 올림과 내림이라고 부른다. 번역 그대로 ceil은 근삿값을 구하는 자릿수에서 값을 올리고, floor는 내린다. 따라서 ceil(2.5)는 3이 되고 floor(2.5)는 2가 된다. 또한, 이 둘은 양의 무한과 음의 무한을 향해 가는 모습이기 때문에 각각 round towards positive infinityround towards negative infinity라고 불리기도 한다.

이와 구현이 비슷한 것으로 truncate가 있다. truncate는 보통 버림이라고 번역되는데, 이는 근삿값을 구할 자릿수 아래의 값을 버린다. 따라서 truncate(2.5)는 2가 된다. 이는 floor와 같아 보이지만 다른 구현이다. 예를 들어 floor(-2.5)의 경우 이는 음의 무한을 향해 가기 때문에 -3이 되지만, truncate(-2.5)는 자릿수를 버려 -2가 된다. 이는 0에 가까운 수를 고르는 것 같은 동작을 하므로 round towards zero라고 부른다. 이와는 반대로 0에서부터 멀어지는 방향으로 움직이는 round away from zero라고 부르는 방법도 있지만, truncate에 비해 딱히 특색이 있지는 않아서 잘 쓰이지 않는다.

지금까지 설명한 ceil, floor, truncate, round away from zero는 모두 큰 쪽이거나 작은 쪽이거나 한 방향으로 움직인다. 하지만 이보다 더 많이 사용되는 것은 가까운 정수(to nearest)로 만드는 것이다. 예를 들면 round(2.4)의 경우 2와의 거리는 0.4고 3과의 거리는 0.6이기 때문에 2가 되고, round(-6.1)의 경우 -6과의 거리는 0.1이고, -7과의 거리는 0.9이기 때문에 -6이 된다. 사실 대부분의 수학 라이브러리에서 round 라는 이름의 함수는 이런 방식으로 동작한다.

가장 가까운 수를 구하는 to nearest round에서는 반드시 정해야 하는 것이 있다. 만약 가장 가까운 수가 2개 있으면 어떤 수를 돌려줘야 할까? 이를 tie-breaking이라고 하는데 가장 많이 쓰이는 방법은 반올림이라고 알려진, 둘 사이의 거리가 같을 경우 위쪽 수를 돌려주는 half up 방식이다. half up 방법에서는 2.5의 경우는 3이 3.5는 4가 된다. 반대로 두 수 사이의 거리가 같으면 아래쪽 수를 선택하는 half down이라고 불리는 방법도 있고, 0을 기준으로 가까운 수를 선택하는 half towards zero나 0에서 먼 수를 선택하는 half away from zero도 있지만, half up 방식보다 장점이 없어서 잘 사용되지 않는다.

일반적으로 half up은 널리 사용되는 round 방식이지만, 근본적으로 큰 문제가 있다. 둘 중 거리가 같을 때 위쪽 수를 택하기 때문에 일렬의 수 집합을 전부 반올림하면 전체적으로 값이 한쪽으로 평향되는 문제가 생긴다. 이 문제를 해결하기 위해 사용되는 방법이 half to even이다. half to even은 두 수 사이의 거리가 같을 경우 짝수를 선택하는 방법이다. 즉, round(2.5)는 2가 되고 round(3.5)는 4가 된다. 이는 IEEE 754에서 기본값으로 사용되는 방법이기도 하다.

비슷한 방식이지만 홀수를 택하는 half to odd라는 방법도 있지만, half to even에 비해서 딱히 장점이 없어서 잘 사용되지 않는다.

half to even이나 half to odd의 경우 잘 분포돼있는 입력에 대해서 round한 값의 평균이나 합이 편중되지 않은 것을 보장하지만, 개별적인 값의 분포를 봤을 때 짝수나 홀수로 편중되는 결과가 나올 수 있다. 이런 문제를 해결하기 위한 방법으로 stochastic round가 있다.

stochastic round는 가까운 정수로 보내는데 둘 사이의 거리가 같을 때 어느 수를 택할지 확률적으로 결정하는 것이다. 이는 편향성을 높은 확률로 제거할 수 있다. 하지만 랜덤하게 실행마다 결과가 달라진다는 점 때문에 거의 사용되지 않는다.

이런 랜덤성을 최소화하기 위해 사용되는 방법이 alternate round라는 방법이다. alternate round는 둘 사이의 거리가 같을 때 한 번은 홀수 한 번은 짝수를 선택하는 방법이다. 처음 선택은 1/2 확률로 선택해도 되고 아니면 홀수나 짝수로 고정해도 된다. alternate round를 이용하면 랜덤성을 최소화하며 편향을 없앨 수 있다. 하지만 round 함수가 불리는 순서가 같을 것이 보장돼야 한다는 제약이 있다.

이래저래 다양한 방법을 설명했지만, IEEE 754에서 제시하는 표준은 ceil, floor, truncate, half up, half to even이고 기본 값은 half to even이기 때문에 보통 수학 라이브러리는 이 5개의 함수 정도만을 제공한다. 대부분의 경우 tie break에서 생기는 편향이 큰 문제가 되지 않기 때문에 이는 별문제가 되지 않지만 만약 tie break의 편향성이 걱정된다면, stochastic roundalternate round를 고려해봐야 한다.

댓글

이 블로그의 인기 게시물

[C++] enum class - 안전하고 쓰기 쉬운 enum

Log Aggregator 비교 - Scribe, Flume, Fluentd, logstash

RAII는 무엇인가

[Python] cache 데코레이터로 최적화하기

[Web] SpeechSynthesis - TTS API