Dua chapter terakhir memakai epsilon dan max_digits10 seadanya; sekarang saatnya membongkar seluruh kotak peralatannya: std::numeric_limits dari header <limits> — template class yang menyimpan karakteristik setiap tipe aritmetika dalam bentuk konstanta compile-time. Daripada menghafal bahwa int berhenti di 2147483647 atau double punya 17 digit round-trip, kode Anda bisa bertanya ke tipe sendiri — dan tetap benar begitu tipe diganti ke long long atau platform lain. Chapter ini memetakan anggota-anggota pentingnya (batas atas dan bawah, epsilon, round_error, infinity, quiet_NaN, denorm_min, has_infinity), membuktikan jebakan klasik min() yang artinya berbeda antara integer dan floating-point, serta menjernihkan pasangan yang paling sering tertukar: digits10 versus max_digits10.

Peta Anggota numeric_limits #

AnggotaArtiintdouble
max()nilai terbesar yang bisa disimpan2147483647≈ 1,80 × 10³⁰⁸
lowest()nilai terkecil paling negatif-2147483648≈ -1,80 × 10³⁰⁸
min()nilai terkecil positif (floating) / sama dengan lowest (integer)-2147483648≈ 2,23 × 10⁻³⁰⁸
denorm_min()nilai positif terhalus, termasuk denormal0≈ 4,94 × 10⁻³²⁴
epsilon()jarak 1.0 ke nilai berikutnya0≈ 2,22 × 10⁻¹⁶
round_error()galat pembulatan terbesar (½ ulp)00,5
digits10digit desimal yang dijamin tersimpan915
max_digits10digit desimal untuk round-trip penuh—17
has_infinity / infinity()dukungan dan nilai infinityfalse / 0true / inf
quiet_NaN()NaN “tenang” yang menular tanpa crash0nan

Semua anggota di atas adalah static constexpr — nilainya melekat pada tipe, bukan objek, sehingga aksesnya ditulis std::numeric_limits<double>::max() tanpa instansiasi. Untuk tipe non-aritmetika (misalnya class buatan Anda), template primernya sengaja mengembalikan nilai netral seperti 0 dan false; spesialisasi khusus hanya ada untuk tipe fundamental.

Membaca Batas dari Kode #

Program berikut memanggil semua anggota di atas dan menguji dua perilaku yang tidak bisa ditebak dari namanya: denorm_min() yang habis menjadi nol saat dibagi dua, dan round-trip presisi yang menuntut max_digits10:

cpp
#include <iomanip>
#include <iostream>
#include <limits>

int main() {
    std::cout << std::boolalpha;

    // 1. Integer: batas simetris, tanpa epsilon bermakna
    std::cout << "int  max    : " << std::numeric_limits<int>::max() << "\n";
    std::cout << "int  lowest : " << std::numeric_limits<int>::lowest() << "\n";
    std::cout << "ll   max    : "
              << std::numeric_limits<long long>::max() << "\n";

    // 2. Floating: empat "batas" yang berbeda makna
    std::cout << "dbl max     : " << std::numeric_limits<double>::max() << "\n";
    std::cout << "dbl lowest  : "
              << std::numeric_limits<double>::lowest() << "\n";
    std::cout << "dbl min     : " << std::numeric_limits<double>::min() << "\n";
    std::cout << "dbl denorm  : "
              << std::numeric_limits<double>::denorm_min() << "\n";

    // 3. Presisi: epsilon, round_error, digits10 vs max_digits10
    std::cout << "dbl epsilon : "
              << std::numeric_limits<double>::epsilon() << "\n";
    std::cout << "dbl round   : "
              << std::numeric_limits<double>::round_error() << "\n";
    std::cout << "digits10    : "
              << std::numeric_limits<double>::digits10 << "\n";
    std::cout << "max_digits10: "
              << std::numeric_limits<double>::max_digits10 << "\n";

    // 4. Nilai khusus
    std::cout << "has_infinity: "
              << std::numeric_limits<double>::has_infinity << "\n";
    std::cout << "infinity    : "
              << std::numeric_limits<double>::infinity() << "\n";
    std::cout << "quiet_NaN   : "
              << std::numeric_limits<double>::quiet_NaN() << "\n";

    // 5. denorm_min adalah nilai positif terhalus: setengahnya jatuh ke nol
    double halus = std::numeric_limits<double>::denorm_min();
    std::cout << "denorm/2    : " << halus / 2.0 << "\n";

    // 6. Round-trip butuh max_digits10, bukan digits10
    std::cout << std::setprecision(std::numeric_limits<double>::digits10);
    std::cout << "0.1 @15     : " << 0.1 << "\n";
    std::cout << std::setprecision(std::numeric_limits<double>::max_digits10);
    std::cout << "0.1 @17     : " << 0.1 << "\n";

    return 0;
}

Output yang Dihasilkan:

text
int  max    : 2147483647
int  lowest : -2147483648
ll   max    : 9223372036854775807
dbl max     : 1.79769e+308
dbl lowest  : -1.79769e+308
dbl min     : 2.22507e-308
dbl denorm  : 4.94066e-324
dbl epsilon : 2.22045e-16
dbl round   : 0.5
digits10    : 15
max_digits10: 17
has_infinity: true
infinity    : inf
quiet_NaN   : nan
denorm/2    : 0
0.1 @15     : 0.1
0.1 @17     : 0.10000000000000001

Bedah baris-per-baris:

  1. Baris 6 (std::cout << std::boolalpha;): mengubah pencetakan boolean dari 1/0 menjadi true/false, sehingga has_infinity terbaca sebagai kata, bukan angka.
  2. Baris 9-11 (std::numeric_limits<int>::max() ... lowest()): batas integer simetris sempurna (2³¹ - 1 dan -2³¹), dan long long terbaca 9223372036854775807 — sekitar 9,2 triliun, seperti yang kita gunakan saat berdebat memilih tipe lebih besar di Chapter Integer Overflow.
  3. Baris 15-18 (max() ... lowest() ... min()): perhatikan dbl min = 2,23 × 10⁻³⁰⁸ adalah nilai positif terkecil ternormalisasi, bukan nilai paling negatif. Inilah jebakan klasik: untuk floating-point, min() dan lowest() berbeda; untuk integer keduanya sama.
  4. Baris 19-20 (denorm_min()): nilai positif paling halus di bawah batas normal — representasi denormal yang menurunkan jumlah bit efektif mantissa demi mendekati nol secara bertahap (gradual underflow).
  5. Baris 23-26 (epsilon() ... round_error()): epsilon 2,22 × 10⁻¹⁶ adalah ulp di sekitar 1.0 yang kita pakai untuk toleransi relatif; round_error = 0,5 adalah galat pembulatan maksimum (setengah ulp) untuk mode pembulatan ke terdekat.
  6. Baris 33-38 (has_infinity ... infinity() ... quiet_NaN()): platform ini laporan has_infinity = true dan nilai khususnya tercetak inf dan nan — konsisten dengan hasil 1.0/0.0 dan 0.0/0.0 pada Chapter Floating Point Representation.
  7. Baris 41-42 (double halus = ...denorm_min(); ... halus / 2.0): membagi dua nilai terhalus itu jatuh ke 0 — tepat di bawah batas terakhir representasi. Ini bukan crash maupun NaN; underflow bertahap memilih nilai terdekat yang tersedia: nol.
  8. Baris 45-48 (setprecision(...digits10 ... max_digits10)): dengan 15 digit, 0,1 tercetak 0.1 — cukup rapi untuk manusia; dengan 17 digit tercetak 0.10000000000000001, satu-satunya bentuk yang menjamin membaca teks kembali ke bit double yang identik.

digits10 vs max_digits10: Dua Janji Berbeda #

digits10 = 15 menjanjikan: bila Anda menyimpan double lalu mencetaknya dengan 15 digit, nilainya masih “berada di lingkaran yang benar” — terbaca kembali menjadi double yang sama. max_digits10 = 17 menjanjikan yang lebih kuat: setiap double apa pun dapat dibedakan dari tetangganya dalam teks. Keduanya benar sekaligus karena beberapa nilai double butuh 17 digit agar tidak bertabrakan dengan tetangganya saat ditulis. Konsekuensi praktisnya sederhana: cetak untuk manusia → 15 digit (atau default 6); simpan ke file/JSON untuk dibaca kembali oleh mesin → 17 digit, selalu.

Dengan peta batas tipe yang lengkap, bagian numerik kita beralih dari angka yang pasti ke angka yang sengaja dibuat tidak pasti: bilangan acak. Header <random> menyediakan mesin dan distribusi yang berkualitas jauh di atas rand() warisan — dan yang lebih penting untuk pengujian, urutannya bisa dibuat persis ulang dengan seed tetap.

Rujukan resmi untuk topik Chapter ini: std::numeric_limits dan overview numerik C++ di cppreference.