Math

Math #

PHP memiliki library matematika yang kaya — dari operasi dasar seperti abs() dan round(), hingga fungsi trigonometri, logaritma, dan dua library presisi tinggi: BCMath untuk desimal presisi arbitrer dan GMP untuk bilangan bulat sangat besar. Yang sering diabaikan developer PHP adalah masalah presisi floating point — 0.1 + 0.2 !== 0.3 bukan bug PHP, melainkan sifat fundamental representasi IEEE 754 yang berlaku di semua bahasa. Mengetahui kapan harus menggunakan float biasa dan kapan harus beralih ke BCMath adalah perbedaan antara aplikasi keuangan yang benar dan yang punya bug subtle yang sangat mahal.

Operasi Dasar #

<?php
// Nilai absolut
echo abs(-42);      // 42
echo abs(-3.14);    // 3.14
echo abs(42);       // 42

// Modulo — sudah dibahas di operator, tapi ada fungsi fmod() untuk float
echo 10 % 3;        // 1 (integer modulo)
echo fmod(10.5, 3); // 1.5 (float modulo)
echo fmod(-10, 3);  // -1 (tanda mengikuti pembilang)

// Pembagian integer (PHP 7+)
echo intdiv(17, 5);  // 3 (dibulatkan ke bawah)
echo intdiv(-17, 5); // -3 (bukan -4!)

// Pangkat
echo pow(2, 10);    // 1024
echo 2 ** 10;       // 1024 (operator, lebih idiomatik)
echo sqrt(144);     // 12.0
echo sqrt(2);       // 1.4142135623731

// Logaritma
echo log(M_E);       // 1.0 — log natural
echo log(100, 10);   // 2.0 — log basis 10
echo log10(1000);    // 3.0 — shorthand log basis 10
echo log(8, 2);      // 3.0 — log basis 2
echo log2(8);        // 3.0 — shorthand log basis 2 (PHP 5.6+)

// Min dan max
echo min(3, 1, 4, 1, 5, 9, 2, 6); // 1
echo max(3, 1, 4, 1, 5, 9, 2, 6); // 9

// Dari array
$angka = [5, 3, 8, 1, 9, 2, 7];
echo min($angka); // 1
echo max($angka); // 9

// Menjumlahkan array
echo array_sum($angka);     // 35
echo array_product([1,2,3,4,5]); // 120 (1×2×3×4×5)

Pembulatan #

PHP memiliki empat strategi pembulatan yang berbeda — pilihan yang salah bisa menghasilkan kalkulasi yang tidak akurat:

<?php
// round() — bulatkan ke angka desimal tertentu
echo round(3.4);    // 3.0 — ke bawah (< 0.5)
echo round(3.5);    // 4.0 — ke atas (>= 0.5)
echo round(3.55, 1); // 3.6
echo round(3.45, 1); // 3.5 — bisa mengejutkan karena 3.45 sebenarnya 3.4499...

// Presisi desimal
echo round(1234.5678, 2);  // 1234.57
echo round(1234.5678, -2); // 1200.0 — bulatkan ke ratusan

// Mode pembulatan
echo round(2.5, 0, PHP_ROUND_HALF_UP);    // 3 — standar (default)
echo round(2.5, 0, PHP_ROUND_HALF_DOWN);  // 2 — ke bawah saat tepat 0.5
echo round(2.5, 0, PHP_ROUND_HALF_EVEN);  // 2 — banker's rounding (ke genap)
echo round(3.5, 0, PHP_ROUND_HALF_EVEN);  // 4
echo round(2.5, 0, PHP_ROUND_HALF_ODD);   // 3 — ke ganjil

// ceil() — selalu ke atas (ceiling)
echo ceil(4.1);   // 5.0
echo ceil(4.9);   // 5.0
echo ceil(-4.1);  // -4.0 (ke atas = menuju 0 untuk negatif)
echo ceil(-4.9);  // -4.0

// floor() — selalu ke bawah (floor)
echo floor(4.9);  // 4.0
echo floor(4.1);  // 4.0
echo floor(-4.1); // -5.0 (ke bawah = menjauh dari 0 untuk negatif)
echo floor(-4.9); // -5.0

// intval() / (int) — truncate (potong, bukan bulatkan)
echo (int) 4.9;   // 4 (potong desimal)
echo (int) -4.9;  // -4 (potong menuju 0)

// Tabel perbedaan untuk nilai negatif
$nilai = -4.5;
echo round($nilai);    // -5.0 (round half away from zero)
echo ceil($nilai);     // -4.0 (ke atas = menuju 0)
echo floor($nilai);    // -5.0 (ke bawah = menjauh dari 0)
echo (int) $nilai;     // -4 (truncate menuju 0)

Jebakan Presisi Floating Point #

<?php
// Masalah presisi IEEE 754 yang terkenal
var_dump(0.1 + 0.2 == 0.3);    // bool(false) — mengejutkan!
var_dump(0.1 + 0.2);           // float(0.30000000000000004)

// Ini bukan bug PHP — semua bahasa yang pakai IEEE 754 punya masalah ini

// Solusi 1: gunakan epsilon untuk perbandingan float
function floatEqual(float $a, float $b, float $epsilon = PHP_FLOAT_EPSILON): bool
{
    return abs($a - $b) < $epsilon;
}

var_dump(floatEqual(0.1 + 0.2, 0.3)); // bool(true)

// Solusi 2: round sebelum bandingkan
var_dump(round(0.1 + 0.2, 10) === round(0.3, 10)); // bool(true)

// Solusi 3: gunakan BCMath untuk kalkulasi penting
echo bcadd('0.1', '0.2', 10); // "0.3000000000" — presisi sempurna

// Kasus nyata yang sering terjadi
$harga = 19.99;
$qty   = 3;
echo $harga * $qty;              // 59.97 — kebetulan benar
echo $harga * $qty == 59.97;    // mungkin false!

// Aman: BCMath atau integer sen
$hargaSen = 1999;  // 19.99 dalam sen
$total    = $hargaSen * $qty;   // 5997 sen = 59.97 — selalu tepat

// PHP_FLOAT_EPSILON — nilai terkecil sehingga 1.0 + EPSILON !== 1.0
echo PHP_FLOAT_EPSILON; // 2.2204460492503E-16

Bilangan Acak #

PHP memiliki dua jenis fungsi random: yang biasa (untuk simulasi/game) dan yang kriptografis (untuk token keamanan):

<?php
// rand() dan mt_rand() — TIDAK untuk keperluan keamanan!
echo rand();           // integer acak antara 0 dan getrandmax()
echo rand(1, 100);     // integer acak antara 1 dan 100
echo mt_rand(1, 100);  // lebih cepat dan distribusi lebih baik

// random_int() — kriptografis, untuk keperluan keamanan
// Gunakan ini untuk token, OTP, password reset, dll.
echo random_int(100000, 999999); // OTP 6 digit yang aman
echo random_int(1, PHP_INT_MAX); // integer acak yang aman

// random_bytes() — byte acak untuk token
$token = bin2hex(random_bytes(32)); // 64 karakter hex
// Berguna untuk: CSRF token, session token, password reset link

// Shuffle array dengan distribusi yang baik
$kartu = range(1, 52);
shuffle($kartu); // in-place, menggunakan random yang baik

// array_rand — pilih kunci acak dari array
$buah  = ['apel', 'mangga', 'jeruk', 'anggur'];
$kunci = array_rand($buah);         // satu kunci acak
$dua   = array_rand($buah, 2);      // dua kunci acak

// Random float 0.0 sampai 1.0
echo mt_rand() / mt_getrandmax();    // float antara 0.0 dan 1.0

// Random float dalam rentang tertentu
function randomFloat(float $min, float $max): float
{
    return $min + mt_rand() / mt_getrandmax() * ($max - $min);
}
echo randomFloat(1.5, 9.9); // float antara 1.5 dan 9.9

Trigonometri #

<?php
// Semua fungsi trig menggunakan RADIAN, bukan derajat
$derajat = 45;
$radian  = deg2rad($derajat); // konversi derajat ke radian

echo sin(deg2rad(30));  // 0.5 — sin 30°
echo cos(deg2rad(60));  // 0.5 — cos 60°
echo tan(deg2rad(45));  // 1.0 — tan 45°

// Invers
echo rad2deg(asin(0.5)); // 30.0 — arcsin(0.5) = 30°
echo rad2deg(acos(0.5)); // 60.0 — arccos(0.5) = 60°
echo rad2deg(atan(1.0)); // 45.0 — arctan(1) = 45°

// atan2 — arctan dengan dua argumen (lebih robust dari atan)
$y = 1.0; $x = 1.0;
echo rad2deg(atan2($y, $x)); // 45.0

// Hyperbolic
echo sinh(1); // 1.1752011936438
echo cosh(1); // 1.5430806348152
echo tanh(1); // 0.76159415595576

// Hitung jarak antara dua titik koordinat (Euclidean)
function jarakEuclidean(float $x1, float $y1, float $x2, float $y2): float
{
    return sqrt(($x2 - $x1) ** 2 + ($y2 - $y1) ** 2);
}

// Hitung jarak antara dua koordinat GPS (Haversine formula)
function jarakHaversine(float $lat1, float $lng1, float $lat2, float $lng2): float
{
    $r    = 6371; // radius bumi dalam km
    $dLat = deg2rad($lat2 - $lat1);
    $dLng = deg2rad($lng2 - $lng1);
    $a    = sin($dLat / 2) ** 2
          + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng / 2) ** 2;
    return $r * 2 * atan2(sqrt($a), sqrt(1 - $a));
}

$jakarta  = [-6.2088, 106.8456];
$surabaya = [-7.2575, 112.7521];
echo round(jarakHaversine(...$jakarta, ...$surabaya)) . " km\n"; // ~666 km

Konstanta Matematika #

<?php
echo M_PI;        // 3.1415926535898  — π
echo M_E;         // 2.718281828459   — bilangan Euler (e)
echo M_LOG2E;     // 1.4426950408890  — log₂(e)
echo M_LOG10E;    // 0.43429448190325 — log₁₀(e)
echo M_LN2;       // 0.69314718055995 — ln(2)
echo M_LN10;      // 2.302585092994   — ln(10)
echo M_SQRT2;     // 1.4142135623731  — √2
echo M_SQRT3;     // 1.7320508075689  — √3
echo M_1_PI;      // 0.31830988618379 — 1/π
echo M_2_PI;      // 0.63661977236758 — 2/π
echo M_SQRT1_2;   // 0.70710678118655 — 1/√2

echo PHP_INT_MAX;      // 9223372036854775807
echo PHP_INT_MIN;      // -9223372036854775808
echo PHP_INT_SIZE;     // 8 (byte)
echo PHP_FLOAT_MAX;    // 1.7976931348623E+308
echo PHP_FLOAT_MIN;    // 2.2250738585072E-308
echo PHP_FLOAT_EPSILON;// 2.2204460492503E-16
echo INF;              // INF
echo NAN;              // NAN
echo -INF;             // -INF

// Cek nilai khusus
var_dump(is_finite(42.0));    // bool(true)
var_dump(is_infinite(INF));   // bool(true)
var_dump(is_nan(NAN));        // bool(true)
var_dump(is_nan(sqrt(-1)));   // bool(true)
var_dump(is_numeric("42"));   // bool(true)
var_dump(is_numeric("42.5")); // bool(true)
var_dump(is_numeric("0x1A")); // bool(false) di PHP 7+

BCMath — Presisi Arbitrer #

BCMath (Binary Calculator) adalah library untuk kalkulasi desimal dengan presisi yang bisa ditentukan secara arbitrer — wajib untuk aplikasi keuangan:

<?php
// Argumen berupa STRING untuk presisi penuh
// Argumen ketiga adalah jumlah desimal hasil

// Operasi dasar BCMath
echo bcadd('10.1', '20.2', 2);      // "30.30" — penjumlahan
echo bcsub('100.00', '30.75', 2);   // "69.25" — pengurangan
echo bcmul('19.99', '3', 2);        // "59.97" — perkalian
echo bcdiv('100.00', '3', 10);      // "33.3333333333" — pembagian
echo bcmod('100', '7');             // "2" — modulo (hanya integer)
echo bcpow('2', '32', 0);          // "4294967296" — pangkat
echo bcsqrt('2', 10);              // "1.4142135624" — akar kuadrat

// bccomp — perbandingan (return -1, 0, atau 1)
echo bccomp('10.5', '10.50', 2);  // 0 — sama
echo bccomp('10.6', '10.5',  1);  // 1 — lebih besar
echo bccomp('10.4', '10.5',  1);  // -1 — lebih kecil

// Set presisi default
bcscale(4); // semua operasi BC berikutnya pakai 4 desimal
echo bcadd('1', '2');  // "3.0000"

// Contoh nyata: kalkulasi diskon dan PPN
function hitungTotal(string $hargaSatuan, string $qty, string $diskon, string $ppn = '0.11'): array
{
    $subtotal = bcmul($hargaSatuan, $qty, 4);
    $potongan = bcmul($subtotal, $diskon, 4);
    $setelahDiskon = bcsub($subtotal, $potongan, 4);
    $pajak   = bcmul($setelahDiskon, $ppn, 4);
    $total   = bcadd($setelahDiskon, $pajak, 4);

    return [
        'subtotal'       => $subtotal,
        'potongan'       => $potongan,
        'setelah_diskon' => $setelahDiskon,
        'pajak'          => $pajak,
        'total'          => $total,
    ];
}

$hasil = hitungTotal('19999.99', '3', '0.10');
// subtotal: 59999.9700
// potongan: 5999.9970
// setelah_diskon: 53999.9730
// pajak: 5939.9970
// total: 59939.9700

// Format output
function formatRupiah(string $nilai, int $desimal = 0): string
{
    // Konversi BC string ke float untuk number_format (aman karena hanya untuk display)
    return 'Rp ' . number_format((float) $nilai, $desimal, ',', '.');
}
echo formatRupiah($hasil['total']); // "Rp 59.940"

GMP — Bilangan Bulat Sangat Besar #

GMP (GNU Multiple Precision) untuk bilangan bulat yang melampaui PHP_INT_MAX:

<?php
// Bilangan faktorial besar — PHP_INT_MAX tidak cukup
function faktorialGmp(int $n): \GMP
{
    $hasil = gmp_init(1);
    for ($i = 2; $i <= $n; $i++) {
        $hasil = gmp_mul($hasil, gmp_init($i));
    }
    return $hasil;
}

$faktorial100 = faktorialGmp(100);
echo gmp_strval($faktorial100);
// 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

// Operasi GMP
$a = gmp_init('123456789012345678901234567890');
$b = gmp_init('987654321098765432109876543210');

$jumlah = gmp_add($a, $b);
$selisih = gmp_sub($b, $a);
$kali    = gmp_mul($a, $b);
$bagi    = gmp_div_q($b, $a); // quotient
$sisa    = gmp_mod($b, $a);   // modulo

echo gmp_strval($jumlah);  // "1111111110111111111011111111100"

// Perbandingan
$cmp = gmp_cmp($a, $b);  // -1 (a < b)

// GMP berguna untuk kriptografi — RSA dan Diffie-Hellman
// Pangkat modular (digunakan dalam RSA)
$base = gmp_init('2');
$exp  = gmp_init('100');
$mod  = gmp_init('1000000007'); // bilangan prima besar
$hasil = gmp_powm($base, $exp, $mod); // (2^100) mod 1000000007
echo gmp_strval($hasil); // "976371285"

// GCD (Greatest Common Divisor)
$gcd = gmp_gcd(gmp_init(48), gmp_init(18));
echo gmp_strval($gcd); // "6"

// Cek apakah prima
$p = gmp_init('104729'); // bilangan prima
echo gmp_prob_prime($p) > 0 ? "mungkin prima" : "pasti tidak prima";
// Nilai 0: bukan prima, 1: mungkin prima, 2: pasti prima

Pola Kalkulasi Keuangan yang Benar #

<?php
// ANTI-PATTERN: gunakan float untuk uang
$harga     = 19.99;
$qty       = 100;
$total     = $harga * $qty; // mungkin tidak tepat 1999.00
$ppn       = $total * 0.11;
$totalPPN  = $total + $ppn; // mungkin ada error presisi kecil

// BENAR Opsi 1: Simpan dalam satuan terkecil (sen/rupiah bulat)
$hargaSen = 1999; // 19.99 dalam sen
$qty      = 100;
$total    = $hargaSen * $qty;  // 199900 sen = 1999.00
$ppn      = intdiv($total * 11, 100); // 21989 sen = 219.89
$totalPPN = $total + $ppn;     // 221889 sen = 2218.89

// Tampilkan
echo number_format($totalPPN / 100, 2, ',', '.'); // "2.218,89"

// BENAR Opsi 2: BCMath untuk kalkulasi
$harga    = '19.99';
$qty      = '100';
$total    = bcmul($harga, $qty, 4);    // "1999.0000"
$ppn      = bcmul($total, '0.11', 4); // "219.8900"
$totalPPN = bcadd($total, $ppn, 2);   // "2218.89"

echo 'Rp ' . number_format((float) $totalPPN, 2, ',', '.'); // "Rp 2.218,89"

// Pembulatan keuangan — PHP_ROUND_HALF_UP untuk konsistensi
$nilai   = 2218.885;
$bulat   = round($nilai, 2, PHP_ROUND_HALF_UP); // 2218.89
$bcBulat = bcadd(bcsub(bcadd($nilai, '0.005', 3), '0', 2), '0', 2); // via BCMath

Ringkasan #

  • 0.1 + 0.2 !== 0.3 — ini bukan bug, ini sifat IEEE 754. Gunakan epsilon (PHP_FLOAT_EPSILON) untuk perbandingan float, atau hindari float untuk nilai penting.
  • BCMath untuk keuanganbcadd, bcsub, bcmul, bcdiv dengan argumen string memberikan presisi desimal yang tepat tanpa floating point error.
  • Simpan uang sebagai integer (sen/rupiah bulat tanpa koma) — $harga = 1999 (bukan 19.99) adalah pendekatan yang paling aman dan efisien untuk kalkulasi moneter.
  • random_int() dan random_bytes() untuk keamanan — jangan gunakan rand() atau mt_rand() untuk token, OTP, atau password reset. Keduanya tidak kriptografis aman.
  • round() memiliki empat modePHP_ROUND_HALF_EVEN (banker’s rounding) mengurangi bias akumulasi dalam kalkulasi yang melibatkan banyak pembulatan.
  • Semua fungsi trigonometri menggunakan radian — gunakan deg2rad() dan rad2deg() untuk konversi. atan2($y, $x) lebih robust dari atan($y/$x) karena menangani semua kuadran.
  • GMP untuk bilangan di atas PHP_INT_MAX — kriptografi, faktorial besar, dan operasi number theory membutuhkan presisi yang melebihi 64-bit integer.
  • intdiv() untuk pembagian integer yang aman (PHP 7+) — tidak menghasilkan float seperti operator /, dan lebih jelas niatnya dari (int)($a / $b).

← Sebelumnya: IO   Berikutnya: Filter & Validation →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact