부동소수점 값이 정상 범위가 아닌지 확인하는 것은 은근히 손이 가고 신경이 쓰이는 작업이다.
NaN과 Infinite를 구분해야 하는 분야도 있지만, 사실 대부분의 경우에선 구분할 필요까진 없는데, 내장 함수들은 이를 구분하게 되어있다.
C++ 11에 와서야 isfinite() 함수가 추가되어 편하게 쓸 수 있는 수준이 되었지만, 그 전까진 뭔가 2% 부족한 느낌이었다.
isnormal()은 0도 false를 리턴하는 기염을 토했으며, isnan()과 ininf()를 따로 확인해야 했었다.
C#은 아직 isfinite()에 해당하는 함수가 없어서 이런 얘기 자체가 사치스럽게(?) 들리는 상황이다.
그런데, 값이 정상 범위인지를 확인하는 것은 의외로 간단하다.
이는 부동소수점의 구조를 보면 쉽게 이해할 수 있다.
일단 단정도 부동소수점은 아래와 같은 구조를 가진다.
최상위비트(MSB)가 부호이고, 다음 8개 비트가 지수, 다음의 23개 비트가 유효숫자.
여기서 지수의 비트가 모두 1인 경우 유효숫자에 따라 NaN과 Infinite가 구분되는 것이다[각주:1].
즉, MSB를 제외한 상위 8개의 비트가 모두 1인지만 확인하면 된다.
C/C++로 구현하면 아래와 같다.
inline bool IsFloatFinite(const float d)
{
return ((*(int32_t*)(&d) & 0x7f800000) != 0x7f800000);
}
C# 코드는 아래와 같다.
강제 타입 캐스팅을 하려면 unsafe를 선언해야 한다는 점에 주목.
public unsafe static bool IsFloatFinite(float d)
{
return ((*(UInt32*)(&d) & 0x7f800000) != 0x7f800000);
}
배정도 부동소수점은 아래와 같은 구조를 가진다.
최상위비트(MSB)가 부호이고, 다음 11개 비트가 지수, 다음의 52개 비트가 유효숫자.
C/C++ 버전만 구현하면 아래와 같다.
inline bool IsDoubleFinite(const double d)
{
return ((*(int64_t*)(&d) & 0x7ff0000000000000L) != 0x7ff0000000000000L);
}
자유 변형 플러그인에서 구현해보니 C#에서는 특히 상당한 성능 향상을 느낄 수 있었다.
덧. Visual C++의 경우는 80비트 double을 지원하고 있지 않아 따로 구현하진 않았는데, 이 경우는 이 방법을 적용하려면 잔머리(?)가 좀 필요하다.
2의 제곱수가 아니기 떄문에 (uint32_t*)((char *)(&d) + 6) 정도의 캐스팅을 한 뒤 값을 비교해야 한다.
테일러 급수를 이용한 sin() 및 BBP를 이용한 원주율 구현 (0) | 2020.03.22 |
---|---|
테일러 급수로 구현한 ln() 함수 (0) | 2020.03.22 |
세제곱수의 순열(permutation) (0) | 2019.12.03 |
RGB ↔ YCbCr 변환 수식 이야기 (1) | 2018.08.18 |
최신 CPU에선 scalar 연산이 SIMD보다 빠를 수도 있더라 (1) | 2018.05.11 |