지인분이 부동 소수점(Floating Point)과 관련한 글을 가져와 흥미롭게 읽던 중, 한글로 번역된 자료가 있었으면 하는 바람으로 해당 문서를 번역하여 게시합니다. 문장 생략, 추가 등 의역이 일부 포함되어 있으며, 실제 의도와는 다르게 번역된 문장이 있을 수도 있습니다. (전문 번역가가 아닌 점 양해 부탁드립니다 ㅠㅠ)
원문은 아래의 링크에서 보실 수 있습니다.
안녕하세요! 저는 컴퓨터에서 객체가 바이트(bytes)로 어떻게 표현되는지에 대해 글을 쓰려고 했습니다. 이번에는 부동 소수점(floating point)에 대해 써보겠습니다.
저는 부동 소수점 사용의 위험성에 대해 수도 없이 들어왔습니다:
- 덧셈의 결합 법칙이 성립하지 않는다 (예를 들어 x+(y+z)와 (x+y)+z는 다르다)
- 만약 아주 작은 값에 아주 큰 값을 더한다면 정확하지 않은 값을 얻을 수도 있다 (작은 수가 사라짐)
- 매우 큰 정수를 부동 소수점으로 표현할 수가 없다
- NaN(Not a Number) 또는 무한한 값을 전달하여 혼란(chaos)을 일으킬 수 있다
- +0과 -0, 즉 두 가지의 0은 동일한 방식으로 표현되지 않는다
- 비정규값(가장 작은 정상적인 숫자보다는 작지만 0보다는 큰 숫자로, 언더플로우의 차이를 채우는 데 사용)의 비정상성
위에서 말한 것들은 그 자체로 조금 추상적이라고 생각했습니다. 저는 실제 프로그램에서 부동 소수점 오류가 어떻게 발생하는지 구체적인 예시를 알고 싶었습니다.
그래서 저는 마스토돈에 실제 프로그램에서 발생하는 부동 소수점 오류에 대한 예시를 요청했습니다. 아래는 마스토돈에서 받은 몇 가지 예시입니다. 또, 저는 이러한 오류를 완벽하게 볼 수 있는 몇 가지 예제 프로그램을 작성했습니다. 목차는 다음과 같습니다.
부동 소수점(floating point)은 어떻게 작동하는가?
부동 소수점은 나쁘거나 랜덤하지 않다
Example 1 - 멈춰버린 주행계(odometer)
Example 2 - Javascript에서 Tweet ID
Example 3 - 분산(variance) 계산이 잘못됨
Example 4 - 언어마다 다른 부동 소수점 계산
Example 5 - Deep Space Kraken
Example 6 - 정확하지 않은 타임스탬프
Example 7 - 열(columns)로 페이지 나누기
Example 8 - 충돌 검사
위의 예시에는 NaN 또는 +0/-0, 무한한 값, 비정규값은 다루지 않습니다. 하지만 이들이 오류를 일으키지 않아서가 아니라, 그냥 제가 이러한 것들을 다루기에는 너무 귀찮아서(got tired) 그렇습니다. 또 쓰다가 실수할 거 같아서 그렇기도 합니다.
부동 소수점(Floating Point)은 어떻게 작동하는가?
이 글에서는 부동 소수점이 어떻게 작동하는지에 대해서는 길게 적지 않을 겁니다. 제가 몇 년전에 이와 관련하여 만들었던 기본적인 자료를 보여드리겠습니다.
부동 소수점은 나쁘거나 랜덤하지 않다
저는 당신이 이 글을 읽고 '부동 소수점은 나쁘다(bad)'는 결론을 내리지 않길 바랍니다. 부동 소수점은 숫자를 계산하는 데에 있어 엄청난 도구입니다. 수많은 똑똑한 사람들은 컴퓨터에서 숫자 계산을 효율적이고 정확하게 하기 위해 많은 노력을 기울여 왔습니다. 다음 두 가지는 부동 소수점의 잘못이 아니라는 점을 분명히 말씀드리고 싶습니다:
- 컴퓨터로 수치 연산을 할 때는 본질적으로 어느 정도의 근사치(근접값)와 반올림이 수반되며, 특히 효율적으로 계산하기 위해서는 더더욱 그렇습니다. 연산하는 모든 숫자에 대해 임의의 정밀한 값(precision)을 부여할 수는 없습니다.
- 부동 소수점은 표준화(IEEE 754)되어 있으므로 부동 소수점 덧셈과 같은 연산은 결정적(deterministic)입니다 – 제가 알기로는 0.1+0.2는 다른 아키텍처에서도 항상 '정확히' 같은 결과인 0.30000000000000004를 얻을 수 있습니다. 예상한 값이 아닐 수도 있지만, 사실 매우 예측 가능한(predictable) 결과입니다.
이 글의 목표는 부동 소수점에 어떤 종류의 문제가 발생할 수 있는지, 왜 그런 문제가 발생하는지부터 언제 조심해야 하는지 등을 알 수 있도록 하는 것입니다.
이제부터 예제를 살펴보겠습니다.
Example 1 - 멈춰버린 주행계(odometer)
어떤 사람이 주행 거리를 측정하기 위해 32-bit float에 작은 값들을 계속 추가하고 있었다고 합니다. 그런데 그 값이 잘못되었다고 하는군요.
예시를 하나 들어서 설명해볼까요? 주행 거리계에 한 번에 1cm씩 계속 늘려가며 측정을 해본다고 합시다. 그러면 10,000km(=1,000,000,000cm)만큼 움직였다고 하면 어떻게 될까요?
아래는 이를 구현한 C언어 프로그램입니다.
#include <stdio.h>
int main() {
float meters = 0;
int iterations = 100000000;
for (int i = 0; i < iterations; i++) {
meters += 0.01;
}
printf("Expected: %f km\n", 0.01 * iterations / 1000 );
printf("Got: %f km \n", meters / 1000);
}
이에 대한 출력값은 이렇게 나오네요.
Expected: 10000.000000 km
Got: 262.144012 km
뭔가 "확실히" 잘못됐네요. 이건 작은 오류가 아닙니다, 262km는 10,000km에 비해 너무 작아요. 뭐가 잘못됐을까요?
무엇이 잘못됐는가: 부동 소수점 숫자 간의 간격
문제는 이겁니다, 32-bit float에서 262144.0+0.01의 결과는 262144.0라는 겁니다. 즉, 숫자가 부정확할 뿐만 아니라 실제로도 전혀 증가하지 않을 겁니다. 그래서 위의 예시에서 10,000km를 더 이동한다고 해도 주행계에는 여전히 262,144m(=262.144km)에 고정되어 있습니다.
왜 이런 일이 생길까요? 부동 소수점의 숫자가 커질수록 숫자 간의 간격도 더 커지기 때문입니다. 연속된 3개의 32-bit 부동 소수점 숫자를 가져와보겠습니다.
- 262144.0
- 262144.03125
- 262144.0625
이 번호는 https://float.exposed/0x48800000에서 significand 번호를 몇 번 증가시켜 얻었습니다. 위 세 숫자는 연속된 숫자입니다. 즉, 262144.0과 262144.03125 사이에는 32-bit의 부동 소수점 숫자가 없다는 겁니다. 이게 왜 문제일까요?
문제는 262144.03125가 약 262144.0+0.03이라는 것입니다. 따라서 262144.0에 0.01을 더하려고 할 때 다음 숫자로 반올림하는 것이 의미가 없습니다. 따라서 합은 여전히 262144.0입니다.
또한 262,144는 2의 거듭제곱(*2의 18제곱)인 것도 우연이 아닙니다. 부동 소수점 숫자는 2의 거듭제곱에 따라 달라지며, 2^18에서 32-bit 부동 소수점 숫자 사이의 간격은 0.016에서 0.03125로 증가합니다.
이 문제를 해결하는 한 가지 방법: double
64-bit 부동 소수점을 사용하면 이 문제가 해결됩니다. 위 예시의 C 프로그램에서 float을 double로 바꾸면 모든 것이 훨씬 더 잘 작동합니다. 출력은 다음과 같아지네요:
Expected: 10000.000000 km
Got: 9999.999825 km
여전히 약간의 부정확함은 발생합니다. 여기서는 약 17cm(0.000175km) 정도의 오차가 발생하네요. 정밀한 우주 기동을 하는 등 매우 정밀한 값을 요구하는 일이 아니라면 우리가 사용할 주행계에서 이정도면 괜찮을 겁니다.
이 문제를 해결하는 또 다른 방법은 주행 거리를 한 번에 1cm씩 더하는 게 아니라 조금 더 큰 덩어리로, 예를 들어 한 번에 50cm씩. 더 큰 단위를 사용하는 겁니다.
위에서 언급한 두 가지 방법을 다 사용한다면 출력은 다음과 같아집니다:
Expected: 10000.000000 km
Got: 10000.000000 km
'[ IT ] > Develop' 카테고리의 다른 글
Android용 SMS/MMS Extractor (문자 추출 앱) (337) | 2024.07.08 |
---|---|
안드로이드용 트위터 인용 보기 (454) | 2024.01.13 |
네이버 치지직(CHZZK) API [작성 중] (338) | 2023.12.22 |
개발 적당히, 정치 적당히, 일상 적당히, 그냥 뭐든지 적당히만 하는 소프트웨어전공 대학생, 쏘가리입니다. Profile Image by REN (Twt@Ren_S2_)
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!