Int dan Float

Perilaku pembagian integer dan jebakan floating-point.

Daftar isi

Topik ini membahas dua tipe bilangan PHP, yaitu int untuk bilangan bulat dan float untuk bilangan pecahan. Materi yang dibahas meliputi batas nilai dan penulisan literal bilangan bulat, perilaku operator pembagian dan modulo, serta jebakan floating-point yang membuat perbandingan pecahan berakhir tidak sesuai harapan. Setelah topik ini dibaca, pemilihan antara pembagian biasa, intdiv, dan modulo sudah didasarkan pada mekanismenya.

Tipe int#

Tipe int menyimpan bilangan bulat dengan lebar 64 bit pada platform modern, sehingga rentangnya berkisar dari PHP_INT_MIN sampai PHP_INT_MAX. Kedua konstanta itu tersedia langsung dan nilainya tercetak bila diperiksa dengan var_dump. PHP tidak menyediakan tipe bilangan bulat dengan lebar lain, sehingga kebutuhan bilangan lebih besar dari rentang ini diarahkan ke ekstensi khusus seperti BCMath.

Literal bilangan bulat dapat ditulis dalam empat notasi. Notasi desimal ditulis seperti biasa, notasi oktal diawali angka nol, notasi heksadesimal diawali 0x, dan notasi biner diawali 0b. Sejak PHP 7.4, garis bawah boleh disisipkan sebagai pemisah agar angka panjang mudah dibaca.

php
<?php

var_dump(2026);
var_dump(010);
var_dump(0x1A);
var_dump(0b1010);
var_dump(1_000_000);
text Keluaran
int(2026)
int(8)
int(26)
int(10)
int(1000000)

Baris kedua memperlihatkan jebakan notasi oktal. Nilai 010 terbaca sebagai delapan, bukan sepuluh, karena angka nol di depan mengubah notasinya. Kebiasaan menambahkan nol di depan untuk merapikan tampilan dapat mengubah nilai tanpa terlihat, sehingga nol di depan hanya ditulis ketika notasi oktal memang diinginkan.

Batas atas tipe int juga menentukan perilaku penjumlahan. Ketika hasil operasi melewati PHP_INT_MAX, PHP mengonversi hasilnya menjadi float tanpa peringatan apa pun.

php
<?php

echo PHP_INT_MAX . PHP_EOL;
var_dump(PHP_INT_MAX + 1);
text Keluaran
9223372036854775807
float(9.223372036854776E+18)

Tipe float#

Tipe float menyimpan bilangan pecahan dengan presisi ganda (double precision) sesuai standar IEEE 754. Literalnya ditulis dengan tanda titik desimal, notasi ilmiah dengan huruf e, atau keduanya. Nilai 1.5e3 berarti satu koma lima dikalikan sepuluh pangkat tiga.

php
<?php

var_dump(2.5);
var_dump(7.0);
var_dump(1.5e3);
text Keluaran
float(2.5)
float(7)
float(1500)

Dua hal penting terlihat pada keluaran di atas. Bilangan bulat yang ditulis dengan titik desimal tetap bertipe float meskipun nilainya bulat, karena PHP membedakan tipe berdasarkan penulisannya. Sebaliknya, var_dump menampilkan nilai float yang bernilai bulat tanpa angka di belakang titik. Selain itu, tipe float menyediakan nilai khusus INF untuk tak hingga dan NAN untuk hasil aritmetika yang tidak terdefinisi, misalnya akar kuadrat dari bilangan negatif.

Presisi ganda menyimpan bilangan pecahan sebagai perkiraan biner, bukan desimal. Sifat inilah yang menjadi akar seluruh jebakan yang dibahas pada bagian berikutnya, sehingga pecahan sebaiknya selalu diperlakukan sebagai perkiraan.

Pembagian, modulo, dan intdiv#

Operator pembagian / mengembalikan float kecuali kedua operannya bilangan bulat yang habis dibagi. Ketika pembagian tidak habis, hasilnya tetap float tanpa pembulatan apa pun, sedangkan pemotongan ke arah nol tersedia melalui fungsi intdiv. Operator modulo % mengembalikan sisa pembagian bilangan bulat, dan tandanya mengikuti bilangan yang dibagi.

php pembagian.php
<?php

var_dump(10 / 3);
var_dump(10 / 2);
var_dump(intdiv(10, 3));
var_dump(10 % 3);
var_dump(-7 % 3);

try {
    intdiv(10, 0);
} catch (DivisionByZeroError $e) {
    echo $e->getMessage() . PHP_EOL;
}
text Keluaran
float(3.3333333333333335)
int(5)
int(3)
int(1)
int(-1)
Division by zero

Keluaran di atas merangkum empat perilaku. Pembagian 10 / 3 menghasilkan float karena tidak habis, sedangkan 10 / 2 menghasilkan int karena habis dibagi. Fungsi intdiv memotong hasil ke arah nol, sehingga intdiv(-7, 2) menghasilkan -3 dan bukan -4. Pembagian dengan nol tidak menghasilkan nilai khusus, melainkan melempar DivisionByZeroError yang bisa ditangkap.

Operator modulo paling sering digunakan untuk memeriksa keterbagian, misalnya $nomor % 2 === 0 untuk menguji apakah sebuah bilangan genap. Karena tandanya mengikuti bilangan yang dibagi, -7 % 3 menghasilkan -1, sehingga perbandingan hasil modulo terhadap bilangan positif perlu memperhatikan tanda operan pertama.

Jebakan floating-point#

Bilangan pecahan desimal seperti 0.1 tidak dapat dinyatakan tepat dalam biner dengan jumlah bit terbatas, sehingga tersimpan sebagai perkiraan terdekat. Selama perhitungan berlanjut, selisih kecil ini menumpuk dan akhirnya terlihat pada perbandingan.

php jebakan-float.php
<?php

printf("%.17f\n", 0.1 + 0.2);
var_dump(0.1 + 0.2 == 0.3);

$selisih = abs(0.1 + 0.2 - 0.3);
var_dump($selisih < PHP_FLOAT_EPSILON);
text Keluaran
0.30000000000000004
bool(false)
bool(true)

Baris pertama membuktikan bahwa 0.1 + 0.2 tersimpan sebagai 0.30000000000000004, sedangkan baris kedua menunjukkan perbandingan == terhadap 0.3 bernilai false. Baris ketiga memperlihatkan pola perbandingan yang benar, yaitu menghitung selisih absolut dan membandingkannya dengan PHP_FLOAT_EPSILON, yaitu selisih terkecil yang masih dapat dibedakan pada presisi ganda. Pola semacam ini disebut perbandingan dengan toleransi, dan toleransinya dapat diperbesar sesuai kebutuhan aplikasi.

Kesalahan umum#

Tiga kesalahan berikut paling sering muncul saat bekerja dengan bilangan.

  • Membandingkan hasil perhitungan pecahan dengan == atau ===. Perbandingan bernilai false meskipun secara matematis hasilnya sama. Perbaikannya adalah membandingkan selisih absolut dengan toleransi sebagaimana contoh pada bagian sebelumnya.
  • Mengharapkan operator / menghasilkan bilangan bulat. Hasilnya bertipe float pada pembagian yang tidak habis, dan pemotongan hanya terjadi pada intdiv. Perbaikannya adalah memilih intdiv bila hasil yang diinginkan berupa bilangan bulat yang dipotong ke arah nol.
  • Menambahkan angka nol di depan literal dengan asumsi nilai tetap sama. Notasi tersebut berubah menjadi oktal, sehingga 010 bernilai 8. Perbaikannya adalah menuliskan literal desimal tanpa nol di depan.

Ringkasan#

  • Tipe int berlebar 64 bit dengan batas PHP_INT_MAX dan PHP_INT_MIN, dan hasil operasi yang melewati batas berubah menjadi float tanpa peringatan.
  • Literal dengan nol di depan dibaca sebagai oktal, sedangkan notasi 0x dan 0b masing-masing menyatakan heksadesimal dan biner.
  • Operator / menghasilkan float pada pembagian yang tidak habis, intdiv memotong hasil ke arah nol, modulo mengikuti tanda operan pertama, dan pembagian dengan nol melempar DivisionByZeroError.
  • Bilangan pecahan tersimpan sebagai perkiraan biner, sehingga perbandingannya dilakukan dengan toleransi melalui PHP_FLOAT_EPSILON, dan nilai uang tidak disimpan sebagai float.

Referensi#