IEEE 754 부동소수점 변환기
부동소수점 수의 부호·지수·가수 비트를 직접 뒤집어 보고, 반올림된 표시값이 아니라 실제로 저장된 값을 확인합니다.
8 digits
32 bits
비트 구성
비트를 클릭하면 뒤집힙니다.
저장된 값
Normal — the mantissa has an implicit leading 1.
0.100000001490116119384765625This is what the hardware holds. 0.1 is the shortest decimal that reads back as the same value, which is why it is what most languages print.
0x3DCCCCCD0 (+)123 stored, -4 effective50331657.450581e-90.0999999940.10000001값이 조립되는 과정
+ 2-4 × 1.10011001100110011001101 (binary) = 0.100000001490116119384765625부동소수점이 수를 담는 방식
IEEE 754는 실수를 부호, 지수, 가수 세 부분으로 나눠 저장합니다. float32는 각각 1비트, 8비트, 23비트를
차지하고 float64는 1비트, 11비트, 52비트입니다. 값은 대략 ±1.가수 × 2^지수 형태로
복원됩니다.
가수 앞의 1은 저장되지 않습니다. 정규화된 수라면 반드시 1로 시작하므로 적을 필요가 없고, 그만큼 정밀도를 한 비트 벌어들입니다. 이 숨은 비트가 부동소수점 표현이 생각보다 촘촘한 이유입니다.
0.1이 0.1이 아닌 이유
이진 부동소수점은 2의 거듭제곱의 합으로만 값을 만듭니다. 0.5, 0.25, 0.125는 정확하지만 0.1은 그렇게 만들어지지 않습니다. 십진법에서 1/3을 0.333…으로밖에 적을 수 없는 것과 같은 사정입니다.
그래서 0.1f를 저장하면 실제로는 0.100000001490116119384765625가 들어갑니다. 화면에 0.1로
보이는 것은 출력 함수가 다시 반올림하기 때문이고, 이 변환기는 반올림 없이 실제 값을 보여 줍니다.
등호 비교가 위험한 이유가 여기 있습니다. 0.1을 열 번 더한 값은 1.0과 같지 않습니다. 누적된 반올림 오차가 남기 때문입니다. 부동소수점을 비교할 때는 차이가 허용 범위 안인지를 보는 편이 안전합니다.
지수는 편향되어 있습니다
지수 필드에는 실제 지수가 그대로 들어가지 않고 일정한 값이 더해진 상태로 저장됩니다. float32는 127, float64는 1023을 더합니다. 지수가 −5라면 float32의 필드에는 122가 들어갑니다.
음수 지수를 따로 부호로 표현하지 않기 위한 방법이고, 덕분에 부동소수점 비트 패턴을 부호 없는 정수처럼 비교해도 대소 관계가 유지됩니다. 정렬이나 비교를 비트 단위로 최적화할 때 쓰이는 성질입니다.
특수한 값들
- 0 — 지수와 가수가 모두 0. 부호 비트에 따라 +0과 −0이 따로 있습니다.
- 비정규화 수 — 지수가 0이고 가수가 0이 아닌 경우. 숨은 1을 쓰지 않아 0에 아주 가까운 값을 표현하며, 대신 정밀도가 점점 떨어집니다.
- 무한대 — 지수가 전부 1이고 가수가 0. 오버플로와 0으로 나누기의 결과입니다.
- NaN — 지수가 전부 1이고 가수가 0이 아닌 모든 경우. 자기 자신과도 같지 않습니다.
비정규화 수는 일부 MCU에서 성능 문제를 일으킵니다. 하드웨어가 처리하지 못해 예외로 빠지면서 수백 사이클을 소모하는 경우가 있어, 실시간 코드에서는 비정규화 수를 0으로 처리하는 모드를 켜기도 합니다.
정밀도는 값의 크기에 따라 달라집니다
부동소수점의 간격은 일정하지 않습니다. 1 근처에서 float32가 표현할 수 있는 이웃한 값의 간격은 약 1.2×10⁻⁷이지만, 1000만 근처에서는 1입니다. 즉 그 크기에서는 정수조차 모두 담지 못합니다.
누적 연산에서 문제가 되는 지점이 이것입니다. 큰 값에 작은 값을 계속 더하면 어느 순간부터 더해도 값이 변하지 않습니다. 센서 값을 오래 적산해야 한다면 float32의 한계를 먼저 계산해 보는 편이 좋습니다.
고정소수점이라는 선택지
부동소수점 유닛이 없는 MCU라면 부동소수점 연산이 소프트웨어로 처리되어 느립니다. 값의 범위가 미리 정해져 있다면 고정소수점이 더 빠르고 오차도 예측 가능합니다.
반대로 범위를 예측할 수 없거나 아주 큰 값과 아주 작은 값을 함께 다뤄야 한다면 부동소수점이 맞습니다. 선택의 기준은 정밀도가 아니라 범위입니다.
자주 묻는 질문
0.1을 넣었는데 0.100000001490116…이 나옵니다.
그것이 float32가 실제로 담고 있는 값입니다. 0.1은 2의 거듭제곱의 합으로 정확히 표현되지 않아서, 가장 가까운 표현 가능한 값으로 반올림됩니다. printf가 0.1로 보여 주는 것은 출력할 때 다시 반올림하기 때문입니다. 두 값을 비교하다 예상과 다른 결과를 얻었다면 이 차이가 원인인 경우가 많습니다.
float32와 float64 중 무엇을 써야 하나요?
float32는 유효숫자가 약 7자리, float64는 약 16자리입니다. 센서 값을 다루는 펌웨어라면 float32로 충분한 경우가 대부분이고, 부동소수점 유닛이 32비트만 지원하는 MCU에서는 float64가 소프트웨어 에뮬레이션으로 돌아가 수십 배 느려집니다. 누적 연산이 많거나 큰 수와 작은 수를 더해야 한다면 float64가 필요합니다.
NaN이 왜 여러 개인가요?
지수가 전부 1이고 가수가 0이 아니면 NaN이므로, 가수에 들어갈 수 있는 값의 수만큼 서로 다른 비트 패턴이 모두 NaN입니다. 가수의 최상위 비트로 quiet NaN과 signaling NaN을 구분하며, 나머지 비트에 진단 정보를 담는 구현도 있습니다. NaN은 자기 자신과도 같지 않으므로 등호로 검사할 수 없습니다.
음수 0은 왜 있나요?
부호 비트가 나머지와 독립적이라서 0에도 부호가 붙습니다. 비교 연산에서는 +0과 −0이 같다고 판정되지만 비트 패턴은 다르고, 1을 나누면 각각 +무한대와 −무한대가 나옵니다. 언더플로가 어느 방향에서 왔는지를 보존하기 위한 설계입니다.
장비에서 읽은 4바이트를 실수로 바꾸려면?
바이트 순서를 먼저 확인해야 합니다. 같은 float32라도 장비에 따라 바이트가 ABCD, DCBA, BADC, CDAB 중 어느 순서로 나오는지가 다릅니다. 모드버스 레지스터에서 읽은 값이라면 이 사이트의 모드버스 레지스터 디코더가 네 가지 순서를 모두 보여 줍니다.