아래의 예는 어디까지나 생각없이 프로그래밍을 했을 때 발생하는 오류입니다.
당연히 제대로 만들면 할 수 있습니다.
0.1+0.1+0.1+0.1+... = 1?
0.1을 10번 더하면 1일까?
아니니까 글의 제목이 저렇겠지?
당근 빳다 1을 출력한다.
그런데 이게 정말 1인지 확인을 하기 위해서 뒤에 코드를 추가해보자.
num을 출력했더니 1이라고 뜨지만, 정작 1과 같은지 체크를 하면 같지 않아서 "1 맞음"이 뜨지 않아.
심지어 num을 2^100번 거듭제곱을 해보니 num은 1이 아닌 무한대로 발산했지.
즉 num은 1보다 아주 조금 큰 숫자라는 뜻이야.
어째서 0.1을 10번 더했는데 1이 아니라 1보다 아주 조금 큰 숫자가 된 것일까?
(cpu에 따라서 1보다 아주 조금 작은 숫자가 되기도 한다.)
이것은 컴퓨터가 2진법으로 계산을 하기 때문에 일어나는 비극이야.
실제로 계산한 숫자의 값은 아래와 같아.
진짜 1보다 조금 크네..
컴퓨터는 2진법으로 동작한다는 것은 대부분의 사람이 알 거야.
그러면 0.1을 2진법으로 나타내는 방법은?
학식만 되어도 가물가물... 할테니 중간과정을 생략하면
'0.00011001100...'(2) 이 되며, 0011이 반복되는 무한소수야.
무한소수를 유한소수로 나타내는 과정에서 어느정도의 오차가 생기기 때문에
0.1을 10번 더해서는 1이 되지 못하는 것이지.
정수는 그냥 진법교환을 하고 종이에 쓸 수 있는 만큼만 표기하면 된다.
그렇다면 길이가 정해져있지 않아서 어떻게 써야 할지 감이 오지 않는 소수는 어떻게 표현하는 것일까?
이것에 대한 표준은 IEEE 754에서 정의되어 있어.
https://ko.wikipedia.org/wiki/IEEE_754 : 부동소수점 정의만 적혀있음
https://en.wikipedia.org/wiki/IEEE_754 : 754내용이 적혀있음.
요약하면
float은 32비트이며, 1bit는 부호,8bit는 지수,23bit는 가수를 의미한다.
부호는 0일때 양수, 1일때 음수이다.
지수는 그 값에서 127을 더한다.
가수는 제일 처음의 1은 제외한 다른 값을 채운다.
따라서 위의 0.1을 2진수로 표현한 0.00011001100...(2)는
1.1001 1001 1001 1001 1001 1001 * 2^(-4)으로 표현을 해.
float에서 가수(소수부분)는 소수점 아래 23개 밖에 적지 못해.
그래서 저 진하게 쓴 1은 어떻게 되는 것일까?
그게 cpu마다 다를 수 있어서 어떤 cpu는 0이 되고, 어떤 cpu는 무한대가 되버려.
어차피 원래 숫자에서 엄청 작은 숫자라서 큰 상관은 없다고 생각했나봐.
(인텔은 대부분의 cpu가 저 경우 올린다고 배웠어.)
일단 내가 쓰는 cpu는 아래와 같이 된다.
1.1001 1001 1001 1001 1001 101 * 2^(-4)
당연히 이 숫자를 10번 더하면 1보다 아주 조큼 큰 숫자가 되겠지?
만약 버림을 하는 시피유라면
1.1001 1001 1001 1001 1001 100 * 2^(-4)
이라서 10번 더하면 1보다 아주 조금 작은 숫자가 되서 0이 되겠지
2진수의 비극으로 인해서 이러한 문제도 발생해
10 000 000 + (1+...10 000 000번) = 20 000 000?
어째서 천만이 더해진게 아니라 이상한 숫자밖에 더해지지 않았을까?
원리는 위의 0.1을 10번 더했을 때 1이 되지 않는 것과 같아.
23자리 문제지.
이처럼 32비트로 표현할 수 있는 40억가지 숫자안에 소수를 표현하려다보니 한계가 있어.
물론 어마어마하게 큰 64비트, 128비트에 넣어도 부동소수점의 한계상 저러한 문제가 생겨.
이 경우에는 안세봤지만, 00이 마지막 숫자라서 그 뒤에 와야 하는 숫자가 버림되었나봐.
결론.
컴퓨터는 소수계산, 큰수+작은 수에서 정확하게 엉뚱한 답을 낸다.
(그런 오류를 내게 프로그래밍이 가능하다.)
URA!!
일째프로그래밍
아이워너비그린
개념만 알려주기 위한 거니까 넘어가자구
강간범
저건 당연한거 아님?
헤헿
끼에엑
빠빠양
끼에엑
전공생 아니면 까놓고 누가 이딴 걸 알고 있냐.
댕떼닥추
끼에엑
댕떼닥추
끼에엑
그냥 읽판에 짤막하게 관심 있으면 읽을 만한 코딩글 올린 거 그 이상도 이하도 아닌데
븅신한테만댓글담
당연한거 아님?
성찰하는뱁새
Rurien
강간범
Tbps
1234.56789를 출력하면
1234.567871이 나온다
이림
아이워너비그린
0.1 * 10 = 1.0 (정확하게)
큰수+작은수에서 유효숫자 문제 때문에 제대로 나오지 않는 경우에는
작은수끼리 먼저 더한 뒤 큰숫자를 만드는 방법으로 우회하거나,
더 큰 자료형에서 계산하는 방식으로 회피해
위에 덧글 쓴 것처럼 부동소수점 데이터는 ==으로 비교하는게 아니라
변수에서 목표값을 뺀 결과의 절대값이 오차미만일 때 참으로 판단하거나 해
절대값(num-1) < 0.000001 같은 식으로 말이지
일반적인 계산환경에서는 저 문제를 겪을 일은 거의 없어
부동소수점 변수를 ==으로 비교하는 경우를 제외하고 말이지
댕떼닥추
아이워너비그린
cout << 의 경우에는 알아서 적절한 자리수만큼 출력을 해
1.5를 넣으면 1.5라고 뜨고 1.01을 넣으면 1.01이라고
printf에서 %f라고 하면 값과 관계없이 6자리가 뜨는 것과는 달라
구아아아악
나눌수없는것
죽죽
천문학적 숫자를 계산하기 위한 표현 규칙을 임의로 짜서 돌릴 수 있을 뿐이지.
그렇게 되면 그건 CPU랑 아무 상관 없는거라서 저런건 무시해도됨
gogogog
철찐
학습하는 가중치가 소수점단위로 나오더라
근데 프린트찍어보면 생략해서 0으로 표시되도 0이아닌
0.0000000001917173
이런식으로나오길래 찾아본적있음
맏춤뻡빌런
아이워너비그린
비교값-목표값의 절대값이 목표값*오차 보다 작은지 체크해야해
보통 사용하는 오차는 10의 -8승이야
Float의 유효숫자가 7자리거든
URA!!
Math.abs(a-b) < Number.EPSILON
하면 거의 같다고 보는거지.
그리고 요즘 나오는 언어는 대부분 보정해줘서 == 로 비교해도 돌아가는것처럼 보이긴 함.
단지 위에서 말한 인공지능같이 작은 오차에도 민감한 계산을 할땐 중요하게 체크해야함.
육아교육과
근성가이
HIGH SIERRA
100번 거듭제곱인것같다
아이워너비그린
Ret = 1 초기화 후
Ret = Ret*obj
했을때가 100번임
2넣고 손으로 풀어봐라
2의 1승 2승 4승 8승 16승으로 넘어간다
HIGH SIERRA