Chapter Floating-Point sebelumnya mengajarkan cara menulis literal float dan double; chapter ini membuka tutup mesinnya dan menunjukkan bagaimana angka-angka itu benar-benar tersimpan di memori. Standar IEEE 754 yang dipakai hampir semua CPU modern menyimpan setiap floating-point dalam tiga bagian bit: satu bit sign (tanda), sekelompok bit exponent (eksponen), dan sekelompok bit mantissa (signifikannya) — struktur yang membuat penjumlahan dan perkalian bisa dikerjakan oleh rangkaian digital dengan cepat. Konsekuensi paling penting dari format biner ini: banyak angka desimal sederhana seperti 0,1 tidak punya representasi persis, sehingga nilai yang tersimpan selalu pembulatan terdekat. Kita akan membedah bit 0.1f langsung dari memori, membandingkan presisi float 32-bit dan double 64-bit, lalu mengenal dua nilai khusus — inf dan nan — yang dicetak deterministik saat perhitungan keluar jalur.
Anatomi IEEE 754 #
Untuk float (32 bit), pembagian bitnya seperti tabel berikut; double mengikuti pola yang sama dengan 1 bit sign, 11 bit exponent, dan 52 bit mantissa:
| Bagian | float | double | Isinya |
|---|---|---|---|
| sign | 1 bit | 1 bit | 0 = positif, 1 = negatif |
| exponent | 8 bit | 11 bit | eksponen basis 2 yang digeser (bias) 127 (float) atau 1023 (double) |
| mantissa | 23 bit | 52 bit | digit biner setelah tanda “1,” yang tersirat |
Skema ini meniru notasi ilmiah biner: nilai = (-1)^sign × 1.mantissa × 2^exponent. Angka 0,1 desimal misalnya = 1,6 × 2⁻⁴, dan pecahan biner 0,6 = 0,1001100110011… berulang tanpa henti. Karena mantissa hanya punya 23 bit (float), deret berulang itu dipotong dan dibulatkan — inilah sumber selisih 0,1 yang akan kita buktikan lewat kode.
Membedah Bit 0.1f Langsung dari Memori #
Program berikut menyalin pola bit mentah 0.1f ke integer 32-bit, lalu memecahnya menjadi tiga field IEEE 754:
#include <bitset>
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <limits>
int main() {
// 1. Bedah bit: bagaimana 0.1f disimpan (IEEE 754 single precision)
float nilai = 0.1f;
std::uint32_t bit = 0;
std::memcpy(&bit, &nilai, sizeof bit); // salin pola bit mentah
std::cout << "bit lengkap : " << std::bitset<32>(bit) << "\n";
unsigned sign = bit >> 31;
unsigned ekspon = (bit >> 23) & 0xFFu;
unsigned mantis = bit & 0x7FFFFFu;
std::cout << "sign : " << sign << "\n";
std::cout << "exponent : " << std::bitset<8>(ekspon)
<< " (= " << ekspon << ", bias 127 -> "
<< static_cast<int>(ekspon) - 127 << ")\n";
std::cout << "mantissa : " << std::bitset<23>(mantis) << "\n";
// 2. float vs double: derajat ketidaktepatan yang berbeda untuk 0.1
std::cout << std::setprecision(std::numeric_limits<float>::max_digits10);
std::cout << "0.1f : " << nilai << "\n";
std::cout << std::setprecision(std::numeric_limits<double>::max_digits10);
std::cout << "0.1 (double) : " << 0.1 << "\n";
// 3. Nilai khusus: infinity dan NaN
double nol = 0.0;
double inf = 1.0 / nol; // pembagian dengan nol -> infinity
std::cout << "1.0/0.0 : " << inf << "\n";
std::cout << "-1.0/0.0 : " << -inf << "\n";
std::cout << "inf - inf : " << inf - inf << "\n"; // NaN
std::cout << "0.0/0.0 : " << nol / nol << "\n"; // NaN
return 0;
}Output yang Dihasilkan:
bit lengkap : 00111101110011001100110011001101
sign : 0
exponent : 01111011 (= 123, bias 127 -> -4)
mantissa : 10011001100110011001101
0.1f : 0.100000001
0.1 (double) : 0.10000000000000001
1.0/0.0 : inf
-1.0/0.0 : -inf
inf - inf : -nan
0.0/0.0 : -nanBedah baris-per-baris:
- Baris 10-12 (
float nilai = 0.1f; ... std::memcpy(&bit, &nilai, sizeof bit);):std::memcpydari<cstring>menyalin 4 byte memorifloatapa adanya kestd::uint32_ttanpa konversi nilai — trik standar untuk melihat representasi bit mentah (yang lebih “resmi” di C++20 adalahstd::bit_cast). - Baris 14 (
std::bitset<32>(bit)):std::bitsetdari<utility>mengubah integer menjadi rangkaian karakter0/1, sehingga 32 bit tampil berurutan dari yang paling berbobot hingga paling kecil. - Baris 15-17 (
unsigned sign = bit >> 31; ...): tiga baris ini memotong pola bit sesuai tabel anatomi — geser 31 bit untuk sign, geser 23 lalu topeng0xFFuntuk exponent, dan topeng0x7FFFFFuntuk 23 bit mantissa. - Baris 19-21 (
std::cout << "exponent : " ...): field exponent terbaca01111011= 123. Dikurangi bias 127, eksponen sesungguhnya adalah -4 — persis pangkat pada 0,1 = 1,6 × 2⁻⁴. - Baris 22 (
std::bitset<23>(mantis)): mantissa menampilkan pola10011001100...1101— jejak jelas pecahan biner berulang dari 0,6 yang dipotong. Perhatikan bit terakhir1: hasil pembulatan ke atas, yang membuat nilai tersimpan sedikit lebih besar dari 0,1. - Baris 25-28 (
std::setprecision(...max_digits10...)):max_digits10adalah jumlah digit desimal yang diperlukan agar konversi desimal → biner → desimal kembali ke nilai asli (9 untukfloat, 17 untukdouble). Dengan presisi itu,0.1fjujur tercetak0.100000001dan0.1bertipe double tercetak0.10000000000000001— keduanya bukan 0,1 yang persis. - Baris 31-36 (
double nol = 0.0; ... nol / nol): pembagian floating-point dengan nol tidak menghentikan program seperti pada integer, melainkan menghasilkan nilai khusus:infuntuk pembagian bertanda sama,-infuntuk bertanda berlawanan, dannan(not a number) untuk operasi tak tentu sepertiinf - infdan0.0/0.0.
Infinity dan NaN Bekerja Deterministik #
Ketiga baris terakhir output membuktikan bahwa nilai khusus ini mengalir dengan aturan yang pasti, bukan acak: inf tetap inf bila ditambah angka berapa pun, inf - inf jatuh ke nan karena pertanyaannya tidak punya jawaban (“infinity dikurangi infinity”), dan nan menular ke setiap operasi yang disentuhnya. Perilaku baku ini disebut quiet NaN propagation dan berlaku sama di semua platform IEEE 754 — program Anda tidak crash, tetapi hasil akhir bisa berupa nan tanpa peringatan apa pun. Karena itu, perhitungan panjang yang menghasilkan nan atau inf sebaiknya diperiksa di titik-titik penting, bukan sekadar di akhir.
Memahami representasi bit membuat chapter-chapter berikutnya terasa masuk akal: kenapa 0.1 + 0.2 tidak sama persis dengan 0.3, kenapa menambah 1 ke angka 16 digit bisa tidak berpengaruh, dan bagaimana membandingkan floating-point dengan benar. Semua itu adalah babak lanjutan dari satu sumber yang sama — presisi terbatas mantissa — dan kita uraikan satu per satu di Chapter Floating Point Precision.
Rujukan resmi untuk topik Chapter ini: overview numerik C++ , std::bitset , dan fundamental types di cppreference.