kodingup.camp

Beranda/JavaScript Mastery: Object & Data

Pengolahan Angka

Modul 8 dari 144 menit bacaNumeric Processing

Isi modul

    Kenapa ini penting

    Hampir setiap aplikasi menghitung sesuatu: total belanja, diskon, pajak, rata-rata nilai, persentase progres, atau jarak. Di JavaScript, semua angka biasa bertipe number yang disimpan dalam format floating point 64-bit (standar IEEE 754). Format ini cepat dan fleksibel, tapi punya keanehan terkenal: 0.1 + 0.2 tidak menghasilkan tepat 0.3.

    Kalau kamu membangun aplikasi kasir atau keuangan dan tidak paham keanehan ini, kamu bisa menampilkan total 30000.000000000004 atau membulatkan uang dengan cara yang salah. Modul ini membahas objek Math, cara membulatkan dengan benar, nilai khusus NaN dan Infinity, pengecekan angka, BigInt untuk bilangan sangat besar, dan cara menampilkan angka dalam format Rupiah yang rapi dengan Intl.NumberFormat.

    Konsep inti

    Masalah presisi floating point

    Komputer menyimpan angka dalam basis 2 (biner). Pecahan seperti 0,1 tidak bisa direpresentasikan tepat dalam biner, sama seperti 1/3 tidak bisa ditulis tepat dalam desimal (0,3333...). Akibatnya 0.1 + 0.2 menjadi 0.30000000000000004.

    Strategi praktis:

    Objek Math

    Math adalah object bawaan berisi konstanta dan function matematika. Kamu tidak perlu membuatnya dengan new.

    Function Fungsi 4.6 -4.6
    Math.round bulat terdekat 5 -5
    Math.floor bulat ke bawah 4 -5
    Math.ceil bulat ke atas 5 -4
    Math.trunc buang desimal 4 -4

    Lainnya: Math.max, Math.min, Math.abs (nilai mutlak), Math.pow atau operator ** (pangkat), Math.sqrt (akar), Math.random() (angka acak 0 sampai kurang dari 1), dan konstanta Math.PI. Perhatikan: Math.round(2.5) hasilnya 3, tapi Math.round(-2.5) hasilnya -2, karena nilai tepat setengah dibulatkan ke arah positif tak hingga.

    toFixed dan pembulatan ke n desimal

    angka.toFixed(n) mengembalikan string dengan n digit desimal. Karena hasilnya string, ubah dengan Number() kalau mau dihitung lagi. Hati-hati, (1.005).toFixed(2) menghasilkan "1.00" bukan "1.01", karena 1.005 sebenarnya tersimpan sebagai 1.00499999... Trik umum untuk membulatkan ke 2 desimal: Math.round((x + Number.EPSILON) * 100) / 100.

    NaN dan Infinity

    BigInt

    Untuk bilangan bulat yang melewati batas aman, misalnya ID transaksi panjang atau perhitungan kriptografi, pakai BigInt dengan menambahkan n di belakang angka (123n) atau BigInt("123"). Aturan BigInt: tidak bisa dicampur langsung dengan number (1n + 1 melempar TypeError), pembagian membuang sisa (7n / 2n hasilnya 3n), dan tidak bisa dipakai dengan Math. JSON.stringify juga melempar error untuk BigInt, jadi ubah ke string dulu.

    Format angka dengan Intl.NumberFormat

    Intl.NumberFormat memformat angka sesuai aturan lokal. Untuk Indonesia ("id-ID"), pemisah ribuan adalah titik dan desimal adalah koma. Dengan style: "currency" dan currency: "IDR", hasilnya seperti "Rp 1.500.000,00". Biasanya Rupiah ditampilkan tanpa desimal, jadi tambahkan maximumFractionDigits: 0. Catatan teknis: spasi setelah "Rp" adalah karakter non-breaking space (), bukan spasi biasa. Ini penting kalau kamu membandingkan string hasil format di dalam test.

    Buat formatter sekali lalu pakai berulang kali (lebih efisien), daripada membuat new Intl.NumberFormat di setiap pemanggilan. Untuk kebutuhan cepat ada juga angka.toLocaleString("id-ID").

    Contoh kode

    // 1) Presisi floating point
    console.log(0.1 + 0.2);         // → 0.30000000000000004
    console.log(0.1 + 0.2 === 0.3); // → false
    const hampirSama = (a, b) => Math.abs(a - b) < Number.EPSILON;
    console.log(hampirSama(0.1 + 0.2, 0.3)); // → true
    
    // Simpan uang bersen sebagai bilangan bulat (sen)
    const hargaSen = 1999; // $19.99
    const totalSen = hargaSen * 3;
    console.log(totalSen / 100); // → 59.97
    
    // 2) Pembulatan dengan Math
    console.log(Math.round(4.6), Math.floor(4.6), Math.ceil(4.1), Math.trunc(4.9));     // → 5 4 5 4
    console.log(Math.round(-4.6), Math.floor(-4.6), Math.ceil(-4.6), Math.trunc(-4.6)); // → -5 -5 -4 -4
    console.log(Math.round(2.5), Math.round(-2.5)); // → 3 -2
    console.log(Math.max(3, 9, 1), Math.min(...[7, 2, 5]), Math.abs(-12)); // → 9 2 12
    console.log(2 ** 10, Math.sqrt(144)); // → 1024 12
    
    // Angka acak bulat antara min dan max (inklusif)
    const acak = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
    const dadu = acak(1, 6);
    console.log(dadu >= 1 && dadu <= 6); // → true
    
    // 3) toFixed mengembalikan string
    const rata = 85.6666;
    console.log(rata.toFixed(2), typeof rata.toFixed(2)); // → 85.67 string
    console.log((1.005).toFixed(2));                      // → 1.00 (bukan 1.01!)
    const bulat2 = (x) => Math.round((x + Number.EPSILON) * 100) / 100;
    console.log(bulat2(1.005)); // → 1.01
    
    // 4) NaN dan Infinity
    console.log(Number("abc"), 0 / 0, 1 / 0, -1 / 0); // → NaN NaN Infinity -Infinity
    console.log(typeof NaN);                          // → number
    console.log(isNaN("abc"), Number.isNaN("abc"));   // → true false
    console.log(Number.isFinite(10), Number.isFinite(Infinity), Number.isFinite("10")); // → true false false
    console.log(Number.isInteger(5), Number.isInteger(5.0), Number.isInteger(5.5));     // → true true false
    console.log(Number.MAX_SAFE_INTEGER);               // → 9007199254740991
    console.log(9007199254740992 === 9007199254740993); // → true (presisi hilang!)
    
    // 5) BigInt
    const besar = 9007199254740993n;
    console.log(besar + 1n);       // → 9007199254740994n
    console.log(7n / 2n, 7n % 2n); // → 3n 1n
    console.log(typeof besar);     // → bigint
    try {
      console.log(besar + 1);
    } catch (err) {
      console.log(err.constructor.name); // → TypeError (tidak bisa campur BigInt dan number)
    }
    console.log(Number(10n) + 1, BigInt(5) * 2n); // → 11 10n
    console.log(JSON.stringify({ id: besar.toString() })); // → {"id":"9007199254740993"}
    
    // 6) Format Rupiah dengan Intl.NumberFormat
    const rupiah = new Intl.NumberFormat("id-ID", {
      style: "currency",
      currency: "IDR",
      maximumFractionDigits: 0,
    });
    console.log(rupiah.format(1500000)); // → Rp 1.500.000
    console.log(rupiah.format(2750.6));  // → Rp 2.751
    console.log(rupiah.format(1500000).includes(" ")); // → true (spasi non-breaking)
    
    const angkaID = new Intl.NumberFormat("id-ID");
    console.log(angkaID.format(1234567.891)); // → 1.234.567,891
    console.log(new Intl.NumberFormat("id-ID", { notation: "compact" }).format(1500000)); // → 1,5 jt
    console.log(new Intl.NumberFormat("id-ID", { style: "percent", maximumFractionDigits: 1 }).format(0.125)); // → 12,5%
    console.log((25000).toLocaleString("id-ID")); // → 25.000
    

    Kesalahan umum

    1. Membandingkan pecahan dengan ===. 0.1 + 0.2 === 0.3 bernilai false. Bandingkan selisihnya dengan toleransi kecil, atau hitung dalam bilangan bulat.
    2. Lupa bahwa toFixed mengembalikan string. (1.5).toFixed(1) + 1 menghasilkan "1.51". Ubah ke angka dulu kalau masih dihitung.
    3. Memakai isNaN global. isNaN("teks") bernilai true karena dikonversi dulu. Pakai Number.isNaN.
    4. Mencampur BigInt dan number. 10n + 5 melempar TypeError. Konversi salah satunya secara eksplisit.
    5. Memformat Rupiah dengan menyambung string manual. "Rp" + angka tidak memberi pemisah ribuan. Pakai Intl.NumberFormat("id-ID", ...) yang dibuat sekali lalu dipakai ulang.

    Latihan

    1. Mudah, Hitung rata-rata dari [78, 85, 92, 67] lalu tampilkan dengan dua desimal.
    2. Sedang, Buat function hitungTotal(harga, diskonPersen, pajakPersen) yang mengembalikan total akhir dalam Rupiah bulat (dibulatkan ke rupiah terdekat), lalu tampilkan dengan format Rupiah tanpa desimal. Uji dengan harga 250000, diskon 15, pajak 11.
    3. Menantang, Buat function faktorial(n) memakai BigInt yang bisa menghitung 25! dengan tepat, lalu bandingkan dengan versi number untuk melihat hilangnya presisi.
    Lihat solusi
    // 1) Mudah
    const nilai = [78, 85, 92, 67];
    let jumlah = 0;
    for (const n of nilai) jumlah += n;
    console.log((jumlah / nilai.length).toFixed(2)); // → 80.50
    
    // 2) Sedang
    const rupiah = new Intl.NumberFormat("id-ID", { style: "currency", currency: "IDR", maximumFractionDigits: 0 });
    function hitungTotal(harga, diskonPersen, pajakPersen) {
      const setelahDiskon = harga * (1 - diskonPersen / 100);
      const total = setelahDiskon * (1 + pajakPersen / 100);
      return Math.round(total);
    }
    const total = hitungTotal(250000, 15, 11);
    console.log(total);                // → 235875
    console.log(rupiah.format(total)); // → Rp 235.875
    
    // 3) Menantang
    function faktorial(n) {
      let hasil = 1n;
      for (let i = 2n; i <= BigInt(n); i++) hasil *= i;
      return hasil;
    }
    function faktorialNumber(n) {
      let hasil = 1;
      for (let i = 2; i <= n; i++) hasil *= i;
      return hasil;
    }
    console.log(faktorial(25));       // → 15511210043330985984000000n
    console.log(faktorialNumber(25)); // → 1.5511210043330986e+25 (sudah tidak tepat)
    

    Ringkasan

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.