Hampir setiap program pasti menghitung sesuatu di suatu tempat: total keranjang, persentase, harga plus pajak, atau memilih sesuatu secara acak dari sebuah daftar. JavaScript menangani semua itu cuma dengan satu tipe angka — dan keputusan tunggal inilah yang menentukan bagaimana angka berperilaku, termasuk satu keanehan yang bikin hampir semua orang bengong saat pertama kali melihatnya. Di artikel ini kamu bakal paham cara kerja angka di balik layar, jebakan floating-point dan cara menghindarinya, serta perkakas bawaan Number dan Math yang bakal sering banget kamu pakai.
Kamu sudah sempat kenalan dengan angka secara singkat di artikel tipe data. Sekarang kita gali lebih dalam sampai bug yang berhubungan dengan angka nggak lagi terasa misterius.
Satu tipe angka untuk semuanya
Kebanyakan bahasa pemrograman memecah angka ke beberapa tipe terpisah — integer, float, double, dan seterusnya. JavaScript memilih jalan yang lebih simpel: cuma ada satu tipe, namanya number, dan tipe itu mencakup bilangan bulat maupun desimal sekaligus.
const umur = 28; // bilangan bulat
const harga = 19.99; // desimal
const negatif = -7; // angka negatif juga oke
const suhu = -3.5;
Kamu nggak perlu mendeklarasikan tipe “integer” atau “float” terpisah — tinggal tulis nilainya saja. Di balik layar, setiap angka di JavaScript disimpan sebagai nilai floating-point 64-bit (formatnya IEEE 754 “double”, kalau suatu saat kamu mau menelusurinya). Praktis memang, tapi inilah juga akar dari keanehan paling terkenal di bahasa ini, yang sebentar lagi kita bahas.
Kamu juga bisa menulis angka dengan beberapa notasi lain kalau memang lebih pas:
const besar = 1_000_000; // garis bawah sebagai pemisah digit (cuma biar enak dibaca)
const hex = 0xff; // heksadesimal → 255
const biner = 0b1010; // biner → 10
const oktal = 0o17; // oktal → 15
const ilmiah = 1.5e6; // notasi ilmiah → 1500000
Garis bawahnya murni soal tampilan — JavaScript mengabaikannya, jadi 1_000_000 dan 1000000 itu angka yang sama persis. Fungsinya cuma bikin angka panjang lebih gampang dibaca.
Jebakan floating-point
Nah, yang ini selalu bikin pemula tersandung. Coba jalankan ini:
console.log(0.1 + 0.2); // 0.30000000000000004 (!)
console.log(0.1 + 0.2 === 0.3); // false
Itu bukan bug JavaScript — hal yang sama terjadi di hampir semua bahasa yang memakai standar floating-point serupa (Python, Java, C, dan lainnya). Penyebabnya: komputer menyimpan angka dalam bentuk biner (basis 2), dan persis seperti 1/3 yang nggak bisa ditulis pas dalam bentuk desimal (0.3333…), nilai 0.1 juga nggak bisa direpresentasikan secara persis dalam biner. Galat pembulatan yang kecil itulah yang bocor di ujung deretan desimal.
Jangan pernah bandingkan desimal langsung pakai ===
Karena pembulatan floating-point, 0.1 + 0.2 === 0.3 itu hasilnya false. Jangan membandingkan hasil desimal pakai === sambil berharap cocok persis. Kalau kamu butuh mengecek apakah dua desimal “cukup sama”, bandingkan selisihnya dengan toleransi yang sangat kecil:
const a = 0.1 + 0.2;
const b = 0.3;
console.log(Math.abs(a - b) < Number.EPSILON); // trueNumber.EPSILON adalah jarak terkecil yang masih berarti antara dua angka yang bisa dibedakan JavaScript — toleransi yang sudah jadi pas buat situasi seperti ini.
Hal ini paling krusial saat berurusan dengan uang. Menyimpan harga sebagai desimal floating-point lalu menjumlahkannya bisa pelan-pelan menumpuk galat pembulatan. Trik yang umum dipakai para profesional adalah bekerja dalam satuan terkecil — simpan nilai sebagai jumlah sen yang utuh (integer), bukan rupiah berkoma, lalu bagi 100 hanya saat menampilkannya. Bilangan bulat nggak kena masalah pembulatan yang sama.
// Daripada pakai rupiah berdesimal:
const total = 19.99 + 5.01; // berisiko menyisakan ekor pembulatan
// Bekerja dalam sen (integer), baru dibagi di akhir:
const totalSen = 1999 + 501; // 2500
const tampilan = (totalSen / 100).toFixed(2); // "25.00"
Method Number yang berguna
Tipe number dibekali sejumlah method praktis untuk memformat dan memeriksa nilai. Dua yang paling sering kamu pakai adalah toFixed dan toString.
toFixed — kunci jumlah desimal
toFixed(n) membulatkan angka ke n angka di belakang koma dan mengembalikannya dalam bentuk string — pas banget buat menampilkan harga dan persentase:
const harga = 19.9;
console.log(harga.toFixed(2)); // "19.90" (berupa string)
const pi = 3.14159;
console.log(pi.toFixed(2)); // "3.14"
console.log((1234.5678).toFixed(0)); // "1235" (dibulatkan)
toFixed mengembalikan string, bukan number
Yang ini sering bikin orang keliru: (19.9).toFixed(2) memberimu string "19.90", bukan angka 19.9. Untuk keperluan menampilkan, biasanya itu justru yang kamu mau — angka nolnya tetap dipertahankan, jadi harga muncul sebagai 19.90, bukan 19.9. Tapi kalau kamu coba menghitung dengan hasilnya, JavaScript bakal menganggap + sebagai penyambungan string: "19.90" + 1 jadi "19.901", bukan 20.9. Format untuk tampilan setelah perhitunganmu selesai, jangan sebelumnya.
toString — ubah ke basis lain
toString() mengubah angka jadi string, dan kamu bisa memberinya basis (radix) untuk mengonversi antar sistem bilangan:
const n = 255;
console.log(n.toString()); // "255" (basis 10, default-nya)
console.log(n.toString(16)); // "ff" (heksadesimal)
console.log(n.toString(2)); // "11111111" (biner)
Ini benar-benar berguna — mengubah angka ke basis 16 itu persis cara kamu menyusun kode warna heksadesimal dari nilai RGB, misalnya.
toLocaleString — format buat dibaca manusia
toLocaleString() memformat angka sesuai kebiasaan pembaca di wilayah tertentu, lengkap dengan pemisah yang tepat:
const jumlah = 1234567.89;
console.log(jumlah.toLocaleString("en-US")); // "1,234,567.89"
console.log(jumlah.toLocaleString("id-ID")); // "1.234.567,89"
// Lebih mantap lagi — format langsung sebagai mata uang:
console.log(jumlah.toLocaleString("id-ID", {
style: "currency",
currency: "IDR",
})); // "Rp1.234.567,89"
Inilah alat yang tepat untuk menampilkan uang dan angka besar ke pengguna. Pemisah ribuan, koma vs titik untuk desimal, sampai simbol mata uang semuanya diurus otomatis berdasarkan locale — jauh lebih rapi ketimbang menyisipkan koma satu per satu secara manual.
Memeriksa angka: NaN dan Infinity
Angka punya dua nilai khusus yang perlu kamu kenal, karena keduanya muncul tepat saat perhitungan melenceng.
NaN — Bukan Angka
NaN artinya “Not a Number” alias bukan angka. Kamu mendapatkannya saat sebuah operasi yang seharusnya menghasilkan angka nggak bisa menghasilkan nilai numerik yang masuk akal:
console.log("halo" * 2); // NaN
console.log(Number("abc")); // NaN
console.log(0 / 0); // NaN
NaN punya sifat yang terkenal aneh: dia nggak sama dengan apa pun, bahkan dengan dirinya sendiri.
console.log(NaN === NaN); // false (!)
Jadi kamu nggak bisa mengeceknya pakai ===. Sebagai gantinya, pakai Number.isNaN():
const hasil = Number("abc"); // NaN
console.log(Number.isNaN(hasil)); // true
Pakai Number.isNaN, bukan isNaN global yang lama
Ada fungsi global lama bernama isNaN() (tanpa awalan Number.). Hindari yang ini — dia lebih dulu mencoba mengonversi argumennya jadi angka, sehingga isNaN("halo") mengembalikan true padahal string itu memang dari awal bukan angka. Number.isNaN() nggak melakukan konversi apa pun: dia hanya mengembalikan true untuk nilai yang benar-benar berupa NaN. Pakai Number.isNaN() dan kamu terhindar dari satu kelompok bug yang halus.
Infinity
Pembagian dengan nol nggak bikin JavaScript crash — hasilnya malah Infinity (atau -Infinity):
console.log(1 / 0); // Infinity
console.log(-1 / 0); // -Infinity
console.log(1 / 0 === Infinity); // true
Untuk memastikan sebuah nilai benar-benar angka yang bisa dipakai — bukan NaN, bukan Infinity — pakai Number.isFinite():
console.log(Number.isFinite(42)); // true
console.log(Number.isFinite(Infinity)); // false
console.log(Number.isFinite(NaN)); // false
Ini penjaga yang bagus sebelum kamu mempercayai hasil sebuah perhitungan atau nilai hasil parsing.
Mengubah string jadi angka
Data dari form, URL, dan API sering datang dalam bentuk teks — "42", bukan 42. Kamu bakal terus-menerus perlu mengonversinya. Ada tiga cara umum, dan ketiganya berperilaku berbeda.
// 1. Number() — ketat, mengonversi seluruh string
console.log(Number("42")); // 42
console.log(Number("42px")); // NaN ("px"-nya merusak)
console.log(Number("3.14")); // 3.14
// 2. parseInt() — membaca integer dari DEPAN, berhenti di karakter aneh
console.log(parseInt("42px")); // 42 (mengabaikan "px")
console.log(parseInt("3.14")); // 3 (berhenti di titik)
// 3. parseFloat() — mirip parseInt tapi mempertahankan desimal
console.log(parseFloat("3.14")); // 3.14
console.log(parseFloat("3.14m"));// 3.14 (mengabaikan "m")
Perbedaannya penting. Number() itu semua-atau-tidak: seluruh string harus tampak seperti angka, kalau tidak hasilnya NaN. parseInt() dan parseFloat() lebih toleran — keduanya membaca sebanyak mungkin angka dari awal lalu diam-diam berhenti di karakter pertama yang nggak cocok. Itu persis yang kamu mau untuk hal seperti "42px" (yang kamu maksud 42), dan persis yang nggak kamu mau saat sebenarnya kamu ingin menangkap input yang salah.
Selalu beri radix pada parseInt
Biasakan memberi parseInt argumen kedua — basisnya — seperti parseInt("42", 10). Tanpa itu, input tertentu (dulu, string yang diawali 0) bisa diartikan dalam basis yang nggak kamu duga. Mesin modern memang umumnya memakai basis 10 secara default, tapi menulis parseInt(value, 10) membuat maksudmu jelas dan menghilangkan keraguan. Cuma menambah empat karakter, tapi membuang satu kelompok kejutan yang nggak perlu.
Ada jalan pintas yang sering kamu lihat di kode beneran: operator + unary mengubah string jadi angka, sama seperti Number().
const teks = "100";
const angka = +teks; // 100, dalam bentuk angka
console.log(angka + 5); // 105
Objek Math
Untuk apa pun yang melampaui aritmetika dasar, JavaScript menyediakan objek bawaan Math — kumpulan konstanta dan fungsi matematika. Kamu nggak perlu membuatnya; dia selalu ada. Kamu memanggil method-nya langsung dari Math.
Pembulatan
Method Math yang paling sering dipakai membulatkan angka dengan cara yang berbeda-beda:
console.log(Math.round(4.5)); // 5 (integer terdekat)
console.log(Math.round(4.4)); // 4
console.log(Math.ceil(4.1)); // 5 (selalu membulatkan KE ATAS)
console.log(Math.floor(4.9)); // 4 (selalu membulatkan KE BAWAH)
console.log(Math.trunc(4.9)); // 4 (cuma membuang desimalnya)
console.log(Math.trunc(-4.9)); // -4 (menuju nol)
Perlu kamu bedakan dengan jelas: round ke yang terdekat, ceil selalu ke atas, floor selalu ke bawah, dan trunc cukup memotong bagian desimal tanpa membulatkan sama sekali. Untuk angka negatif, floor dan trunc beda hasil — Math.floor(-4.9) itu -5, sedangkan Math.trunc(-4.9) itu -4.
Perhitungan yang umum
Sejumlah method Math muncul berulang-ulang:
console.log(Math.abs(-7)); // 7 (nilai mutlak)
console.log(Math.pow(2, 10)); // 1024 (2 pangkat 10)
console.log(2 ** 10); // 1024 (operator ** — sama saja)
console.log(Math.sqrt(144)); // 12 (akar kuadrat)
console.log(Math.max(3, 9, 1)); // 9 (terbesar)
console.log(Math.min(3, 9, 1)); // 1 (terkecil)
Operator pangkat ** melakukan tugas yang sama dengan Math.pow dan terbaca sedikit lebih bersih, jadi sering kali kamu bakal lebih memilihnya. Lalu Math.max/Math.min menerima argumen berapa pun jumlahnya — praktis untuk mencari skor tertinggi atau harga terendah dari sekumpulan nilai.
Cari nilai maksimum array pakai operator spread
Math.max menerima daftar angka, bukan array. Kalau kamu sudah punya array, sebarkan isinya pakai ...:
const skor = [82, 91, 67, 100, 73];
console.log(Math.max(...skor)); // 100
console.log(Math.min(...skor)); // 67... “menyebarkan” isi array jadi argumen-argumen terpisah, seolah-olah kamu mengetik Math.max(82, 91, 67, 100, 73). Ini cara standar untuk mendapatkan nilai terbesar atau terkecil dari sebuah array.
Konstanta Math
Math juga menyimpan beberapa konstanta yang berguna:
console.log(Math.PI); // 3.141592653589793
console.log(Math.E); // 2.718281828459045
// Luas lingkaran dengan jari-jari 5:
const jari = 5;
const luas = Math.PI * jari ** 2;
console.log(luas.toFixed(2)); // "78.54"
Angka acak
Math.random() mengembalikan desimal acak antara 0 (termasuk) dan 1 (tidak termasuk) — jadi nilainya di suatu titik dari 0 sampai mendekati, tapi nggak pernah menyentuh, 1:
console.log(Math.random()); // mis. 0.7263… (beda tiap kali)
Kalau berdiri sendiri, itu jarang jadi yang kamu butuhkan. Yang umum dibutuhkan adalah integer acak dalam rentang tertentu, dan ada resep standar untuk itu:
// Integer acak dari min sampai max (keduanya termasuk):
function angkaAcak(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(angkaAcak(1, 6)); // lemparan dadu: 1–6
console.log(angkaAcak(1, 100)); // 1–100
Baca dari dalam ke luar: Math.random() memberi pecahan, perkalian memperbesarnya seukuran rentangmu, Math.floor memangkasnya jadi bilangan bulat, lalu penambahan min menggesernya agar mulai di titik yang benar. Memilih item acak dari sebuah array pakai ide yang sama:
const warna = ["merah", "hijau", "biru"];
const pilihan = warna[Math.floor(Math.random() * warna.length)];
console.log(pilihan); // salah satu dari tiga, secara acak
Math.random tidak aman untuk keamanan
Math.random() oke buat game, pengacakan, atau memilih tip acak untuk ditampilkan — apa pun yang sekadar “enak kalau tak tertebak”. Tapi dia bukan sumber acak yang aman secara kriptografis, jadi jangan pernah memakainya untuk membuat password, token sesi, atau apa pun yang sensitif secara keamanan. Untuk itu, browser menyediakan crypto.getRandomValues(), yang memang dirancang untuk benar-benar tak tertebak. Sesuaikan alat dengan taruhannya.
Saat integer biasa nggak cukup: BigInt
Angka biasa aman dan presisi hanya sampai batas tertentu — Number.MAX_SAFE_INTEGER, yaitu 9007199254740991 (sedikit di atas 9 kuadriliun). Lewat dari titik itu, bilangan bulat bisa diam-diam kehilangan presisi:
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(9007199254740991 + 1); // 9007199254740992 (oke)
console.log(9007199254740991 + 2); // 9007199254740992 (salah! harusnya ...993)
Untuk kasus langka yang benar-benar butuh bilangan bulat raksasa — ID database yang besar, sebagian pekerjaan finansial atau ilmiah — JavaScript punya BigInt. Cara membuatnya: tambahkan n di akhir literal integer:
const raksasa = 9007199254740991n;
console.log(raksasa + 2n); // 9007199254740993n (benar!)
Catatannya: kamu nggak bisa mencampur BigInt dengan angka biasa dalam satu operasi (10n + 5 bakal melempar error — kamu harus menulis 10n + 5n), dan BigInt nggak bisa menampung desimal. Untuk pekerjaan sehari-hari, cukup pakai angka biasa; gunakan BigInt hanya saat kamu benar-benar menabrak batas safe-integer.
Contoh realistis
Coba kita rangkai beberapa hal tadi jadi sesuatu yang mungkin betulan kamu tulis — ringkasan pesanan kecil:
const pesanan = {
pelanggan: "John Doe",
items: [
{ nama: "Keyboard", harga: 250000, jumlah: 1 },
{ nama: "Mouse", harga: 120000, jumlah: 2 },
],
};
// Jumlahkan harga × jumlah dari tiap item
let subtotal = 0;
for (const item of pesanan.items) {
subtotal += item.harga * item.jumlah;
}
const tarifPajak = 0.11; // pajak 11%
const pajak = subtotal * tarifPajak;
const total = subtotal + pajak;
console.log("Subtotal:", subtotal.toLocaleString("id-ID")); // 490.000
console.log("Pajak:", Math.round(pajak).toLocaleString("id-ID")); // 53.900
console.log("Total:", Math.round(total).toLocaleString("id-ID")); // 543.900
Inilah angka yang benar-benar bekerja: ada perulangan untuk menjumlahkan (for...of dari artikel perulangan), aritmetika untuk pajak, Math.round untuk merapikan hasilnya, dan toLocaleString untuk menyajikannya sesuai cara orang membacanya.
Penutup
Sekarang kamu sudah pegang kendali yang kokoh atas cara angka berperilaku di JavaScript beserta perkakasnya:
- JavaScript punya satu tipe angka untuk bilangan bulat maupun desimal. Di balik layar bentuknya floating-point 64-bit — praktis, tapi jadi sumber satu keanehan klasik.
- Matematika desimal nggak persis (
0.1 + 0.2 !== 0.3). Jangan bandingkan desimal pakai===; untuk uang, bekerjalah dalam satuan terkecil (sen) sebagai integer. - Method Number memformat dan mengonversi:
toFixed(n)untuk desimal tetap (mengembalikan string),toString(radix)untuk basis lain,toLocaleString()untuk format yang ramah dibaca dan format mata uang. NaNartinya “bukan angka” dan nggak sama dengan apa pun, bahkan dirinya sendiri — cek pakaiNumber.isNaN(). Pembagian dengan nol menghasilkanInfinity— jaga hasil yang valid denganNumber.isFinite().- Konversi string pakai
Number()(ketat),parseInt(str, 10), atauparseFloat()(longgar). Selalu beri radix padaparseInt. - Objek
Mathmenangani sisanya:round/ceil/floor/truncuntuk pembulatan,abs/sqrt/max/min/**untuk perhitungan, danMath.random()untuk keacakan (bukan untuk keamanan). - Untuk bilangan bulat melebihi
Number.MAX_SAFE_INTEGER, gunakanBigInt(123n).
Angka dan teks adalah dua bahan mentah yang menyusun hampir semua program. Sekarang kamu sudah menguasai sisi numeriknya, dan di dasar-dasar string dan tipe data kamu sudah menyentuh sisi teksnya. Berikutnya, kita bahas tanggal dan waktu — yang ternyata juga disimpan sebagai angka, jadi semua yang baru saja kamu pelajari di sini langsung kepakai.