Tidak semua angka berupa bilangan bulat: berat badan 61,5 kg, rata-rata nilai 87,25, atau suhu 36,6 derajat membutuhkan bagian pecahan. Tipe floating-point (disingkat float, kadang disebut double) adalah jawaban PHP untuk angka pecahan. Tipe ini cepat dan didukung perangkat keras, tetapi punya satu sifat yang wajib dipahami sejak awal: nilainya disimpan dalam representasi biner yang tidak selalu persis. Pada Chapter ini kita bedah cara kerja float, jebakan presisinya, dan pola aman untuk membandingkan serta membulatkan angka pecahan.

Apa itu Floating-Point #

Float menyimpan bilangan pecahan dalam format standar IEEE 754 double-precision 64-bit. Cara menuliskannya persis seperti matematika: tanda desimal, atau notasi ilmiah dengan e untuk pangkat sepuluh:

php
<?php
$berat = 61.5;
$rataRata = 87.25;
$probabilitas = 0.0001;

var_dump($berat, $rataRata, $probabilitas);
var_dump(is_float($rataRata));

Output yang Dihasilkan:

text
float(61.5)
float(87.25)
float(0.0001)
bool(true)

Notasi ilmiah memudahkan penulisan angka sangat besar atau sangat kecil — 1.5e3 berarti 1,5 × 10³ dan 2.5e-4 berarti 2,5 × 10⁻⁴:

php
<?php
var_dump(1.5e3);
var_dump(2.5e-4);

Output yang Dihasilkan:

text
float(1500)
float(0.00025)

Garis bawah pembaca yang kita pelajari di Chapter Integers juga berlaku untuk float, misalnya 15_500.50 yang nilainya 15500.5.

Presisi Terbatas: Kasus 0.1 + 0.2 #

Inilah bagian terpenting dari chapter ini. Komputer menyimpan float dalam biner, dan tidak semua angka desimal punya representasi biner yang persis — persis seperti ⅓ yang tidak bisa ditulis persis dalam desimal (0,3333…). Amati hasil dua perintah berikut:

php
<?php
echo 0.1 + 0.2, "\n";
var_dump(0.1 + 0.2);

Output yang Dihasilkan:

text
0.3
float(0.30000000000000004)

Dua perintah yang sama-sama “mencetak” 0.1 + 0.2 menghasilkan tampilan berbeda: echo menampilkan 0.3, sedangkan var_dump() menampilkan float(0.30000000000000004). Nilai sebenarnya yang tersimpan di memori adalah yang kedua — echo hanya menampilkannya dengan 14 digit signifikan sehingga selisih kecilnya tersembunyi. Selisih miliaran kali lebih kecil dari nilai aslinya memang tidak terasa untuk kebanyakan perhitungan, tetapi konsekuensinya nyata saat nilai dibandingkan:

php
<?php
var_dump(0.1 + 0.2 == 0.3);
var_dump(0.1 + 0.2 === 0.3);
var_dump((0.1 + 0.2) + 0.3 == 0.1 + (0.2 + 0.3));

Output yang Dihasilkan:

text
bool(false)
bool(false)
bool(false)

Ketiga perbandingan menghasilkan false. Dua baris pertama membuktikan 0.1 + 0.2 tidak pernah persis sama dengan 0.3 di memori. Baris ketiga lebih mengejutkan lagi: penjumlahan yang sama hasilnya berbeda bila urutan pengelompokannya diubah, karena pembulatan terjadi di setiap langkah.

Membandingkan Float dengan Toleransi #

Cara yang benar membandingkan dua float adalah menghitung selisih absolutnya, lalu memeriksa apakah selisih itu lebih kecil dari batas toleransi (epsilon) yang dapat diterima untuk domain masalah Anda:

php
<?php
$hasilPerhitungan = 0.1 + 0.2;
$nilaiEkspektasi = 0.3;
$epsilon = 0.0000001;

var_dump(abs($hasilPerhitungan - $nilaiEkspektasi));
var_dump(abs($hasilPerhitungan - $nilaiEkspektasi) < $epsilon);

Output yang Dihasilkan:

text
float(5.551115123125783E-17)
bool(true)

Fungsi abs() mengembalikan nilai absolut (menghilangkan tanda negatif). Selisih sebenarnya hanya 5.551115123125783E-17 — nol untuk semua keperluan praktis — sehingga perbandingan terhadap epsilon 0.0000001 menghasilkan true. PHP juga menyediakan konstanta PHP_FLOAT_EPSILON (2.220446049250313E-16), yaitu selisih terkecil yang masih dapat dibedakan antara dua float di sekitar angka 1; untuk perhitungan yang telah menumpuk beberapa operasi, gunakan toleransi yang lebih longgar seperti 0.000001 karena galat pembulatan ikut terakumulasi.

Pembulatan: floor, ceil, dan round #

Tiga fungsi pembulatan menutup kebutuhan umum: floor() membulatkan ke bawah, ceil() ke atas, dan round() ke nilai terdekat:

php
<?php
var_dump(floor(3.7), ceil(3.2));
var_dump(floor(-3.2), ceil(-3.7));
var_dump(round(2.5), round(-2.5));
var_dump(round(3.14159, 2));
var_dump(round(1.955, 2));

Output yang Dihasilkan:

text
float(3)
float(4)
float(-4)
float(-3)
float(3)
float(-3)
float(3.14)
float(1.96)

Ada dua hal yang perlu dicermati. Pertama, floor() dan ceil() selalu mengembalikan float — bukan integer — meskipun hasilnya tampak bulat; konversi ke integer tetap tugas Anda. Kedua, round() pada nilai tengah (.5) menjauhi nol: 2.5 menjadi 3 dan -2.5 menjadi -3. Parameter kedua yang opsional membulatkan ke jumlah digit desimal tertentu, seperti round(3.14159, 2) yang menghasilkan 3.14.

NAN dan INF #

Dua nilai float istimewa merepresentasikan kondisi di luar bilangan biasa: INF (tak hingga) dan NAN (not a number). Keduanya muncul dari operasi yang meluber:

php
<?php
var_dump(1e300 * 1e300);
var_dump(sqrt(-1));
var_dump(log(0));

Output yang Dihasilkan:

text
float(INF)
float(NAN)
float(-INF)

Perkalian 1e300 * 1e300 melebihi kemampuan 64-bit float sehingga hasilnya INF; akar kuadrat dari bilangan negatif tidak terdefinisi sehingga menghasilkan NAN; dan log(0) menuju tak hingga negatif -INF. Untuk memeriksanya, gunakan fungsi khusus — bukan perbandingan biasa:

php
<?php
var_dump(is_nan(sqrt(-1)));
var_dump(is_infinite(1e300 * 1e300));
var_dump(is_finite(3.14));
var_dump(NAN == NAN, NAN === NAN);

Output yang Dihasilkan:

text
bool(true)
bool(true)
bool(true)
bool(false)
bool(false)

Perhatikan baris terakhir: NAN dibandingkan dengan apa pun — termasuk dirinya sendiri — selalu menghasilkan false, sesuai standar IEEE 754. Itulah alasan is_nan() adalah satu-satunya cara yang benar untuk mendeteksi NAN, dan is_finite() untuk memastikan sebuah nilai adalah bilangan biasa.

Konversi Float ke Integer #

Cast (int) pada float memotong bagian pecahan ke arah nol (truncation), bukan membulatkan ke bawah:

php
<?php
var_dump((int)3.99);
var_dump((int)-3.99);
var_dump((int)round(3.99));

Output yang Dihasilkan:

text
int(3)
int(-3)
int(4)

Nilai 3.99 menjadi 3 dan -3.99 menjadi -3 — keduanya bergerak menuju nol. Ini berbeda dengan floor(-3.99) yang menghasilkan -4. Bila maksud Anda benar-benar “bulatkan ke bilangan terdekat”, gunakan round() sebelum cast, seperti baris ketiga yang menghasilkan 4.

Satu jebakan tampilan terakhir: json_encode() menampilkan digit presisi penuh, sedangkan konversi ke string biasa memakai 14 digit:

php
<?php
$nilai = 0.1 + 0.2;
var_dump(json_encode($nilai));
var_dump((string)$nilai);

Output yang Dihasilkan:

text
string(19) "0.30000000000000004"
string(3) "0.3"

Ringkasan #

Float merepresentasikan angka pecahan dalam biner IEEE 754 yang tidak selalu persis, sehingga perbandingan kesamaan harus memakai toleransi epsilon, bukan ===. floor(), ceil(), dan round() mengembalikan float dengan aturan pembulatan masing-masing; cast (int) memotong ke arah nol. Nilai khusus INF dan NAN dideteksi lewat is_infinite() dan is_nan(). Dengan integer dan float dikuasai, kita lanjut ke tipe yang paling sering Anda sentuh sehari-hari: string.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

contoh.php

Unduh berkas
php
<?php
// contoh.php — rangkuman Chapter Floating-Point Numbers
// Jalankan dengan: php contoh.php

echo "== Presisi biner ==\n";
echo 0.1 + 0.2, "\n";
var_dump(0.1 + 0.2);
var_dump(0.1 + 0.2 === 0.3);

echo "\n== Perbandingan dengan toleransi ==\n";
$hasil = 0.1 + 0.2;
$ekspektasi = 0.3;
var_dump(abs($hasil - $ekspektasi));
var_dump(abs($hasil - $ekspektasi) < 0.0000001);

echo "\n== Pembulatan ==\n";
var_dump(floor(3.7), ceil(3.2));
var_dump(floor(-3.2), ceil(-3.7));
var_dump(round(2.5), round(-2.5), round(3.14159, 2));

echo "\n== NAN dan INF ==\n";
var_dump(1e300 * 1e300, sqrt(-1), log(0));
var_dump(is_nan(sqrt(-1)), is_infinite(1e300 * 1e300), is_finite(3.14));

echo "\n== Cast ke integer ==\n";
var_dump((int)3.99, (int)-3.99, (int)round(3.99));

echo "\n== Uang dalam satuan sen ==\n";
$hargaSen = 15500050; // Rp155.000,50
$jumlah = 3;
$totalSen = $hargaSen * $jumlah;
echo "Total: Rp" . number_format($totalSen / 100, 2, ",", "."), "\n";