1. 컴퓨터가 실수를 표현하는 방법
* 고정 소수점 : 정수부와 소수부로 나누어서 표현하는 방법
- int와 다를게 없어서 연산속도가 빠르고 시스템이 간단함
- 표현할 수 있는 수의 크기가 작음
- 정밀도를 높이려면 필요한 비트 수가 커짐
* 부동 소수점 : 가수부와 지수부로 나누어서 표현하는 방법
- 넓은 범위의 숫자를 표현할 수 있음
- 상대적으로 정밀도가 높음
- 연산이 복잡해짐
2. 2진법으로 된 부동소수점
IEEE 754 : IEEE에서 공표한 부동 소수점 방식의 표준안
s (부호) : 첫번째 비트, 수의 부호를 결정
m (가수) : 2진수에서는 1.xxxx.. 로 이루어짐
- 단정도(32비트) : 23비트
- 배정도(64비트) : 52비트
e (지수) : m을 정규화 하면 정해지는 값, IEEE 754에서는 비트로 표현하는경우 e + bias가 들어가게 된다
- 단정도(32비트) : 8비트
- 배정도(64비트) : 11비트
* 예시) -27.625
-27.625 (10진수) = 11011.101 (2진수)
s (부호) =>
-1 이므로 비트는 1
m (가수) =>
- 11011.101을 4칸 이동시킴 : 1.1011101
- 소수 부분만 남김 : 1011101
- 나머지는 0으로 채움 : 1011101000...
e (지수) : m을 정규화 하면 정해지는 값, IEEE 754에서는 비트로 표현하는경우 e + bias가 들어가게 된다
- 4칸 이동이므로 2 ^ 4 : e는 4
- IEEE 754에서는 bias값은 127 이므로 4 + 127 = 131
- 이진법 변환 : 10000011
1100000111011101000...
3. 부동소수점 오류
* 0.1을 소수점 9자리까지 출력했을 때
float value = 0.1f;
Console.WriteLine("{0:G9}", value); //0.100000001
=> 실제로 근사치를 갖고 있음
* 0.1과 0.100000001490116119384765625을 비교했을 때
float num = 0.1f;
if(num == 0.100000001490116119384765625)
{
Console.WriteLine("True"); // 출력
}
=> 다음과 같이 정확한 값을 갖고 있지 않아서 연산에도 오류가 생긴다
* int형으로 캐스팅
float value = 43.3f - 43.2f;
Console.WriteLine("{0:G9}", value); //0.0999984741
value = value * 10;
Console.WriteLine("{0:G9}", value); //0.999984741
int valueInt = (int)value;
Console.WriteLine(valueInt); //0
=> 원하는 값은 0.1, 1, 1 이어야하지만 실제로 갖고있는 실수값이 근사값이기 때문에 int로 형변환 시 의도하던 값과 다른 값이 나옴
4. 오류 해결 방법
* float보다는 double 사용
- float의 상대오차 10^-7, double의 상대오차 10^-15
- C# decimal : 부동소수형이 아니고 29자리까지 지원
* 바로 캐스팅하지 말고 작은 숫자만큼 더한 후 캐스팅
* 등호사용 금지(내결함성 허용) : epsilon 사용
double value = 43.3f - 43.2f;
if(value >= 0.1)
{
Console.WriteLine("Yes!");
}
else
{
Console.WriteLine("No!"); // No! 출력
}
if (Math.Abs(value) <= 0.1f + double.Epsilon)
{
Console.WriteLine("Yes!"); // Yes! 출력
}
else
{
Console.WriteLine("No!");
}
=> 최대한 실수형 간 계산을 피하기