Math #

Operasi matematika muncul di hampir setiap aplikasi — menghitung diskon, membulatkan harga, menghasilkan ID acak, menormalisasi data untuk grafik, atau menghitung jarak antara dua koordinat. TypeScript mewarisi objek Math bawaan JavaScript yang menyediakan konstanta matematika umum dan puluhan fungsi siap pakai. Di luar itu, ada sejumlah jebakan yang perlu dipahami — terutama soal floating point yang bisa menghasilkan hasil tak terduga saat bekerja dengan uang atau angka presisi tinggi. Artikel ini membahas seluruh kemampuan Math, karakteristik tipe number di JavaScript, BigInt untuk bilangan bulat yang melampaui batas aman, dan pola kalkulasi yang sering muncul di aplikasi nyata.

Tipe Number di TypeScript #

Sebelum masuk ke Math, penting memahami bagaimana TypeScript dan JavaScript merepresentasikan angka. Semua angka di JavaScript — baik integer maupun desimal — disimpan dalam format IEEE 754 double-precision floating-point 64-bit. Ini bukan kelemahan TypeScript, melainkan standar yang sama digunakan Python, Java, dan hampir semua bahasa modern.

// semua ini adalah tipe number yang sama
const integer: number = 42;
const desimal: number = 3.14;
const negatif: number = -100;
const besar: number = 1_000_000; // underscore sebagai pemisah ribuan (ES2021)
const hex: number = 0xFF;        // 255 dalam hexadecimal
const oktal: number = 0o77;      // 63 dalam oktal
const biner: number = 0b1010;    // 10 dalam biner
const saintifik: number = 1.5e3; // 1500

// nilai spesial
console.log(Infinity);   // tak terhingga
console.log(-Infinity);  // tak terhingga negatif
console.log(NaN);        // Not a Number — hasil operasi tidak valid

// cek nilai spesial
console.log(isFinite(Infinity)); // false
console.log(isFinite(42));       // true
console.log(isNaN(NaN));         // true
console.log(isNaN("abc"));       // true — konversi dulu ke number!

// ANTI-PATTERN: gunakan isNaN global yang koersif
console.log(isNaN("abc")); // ✗ true — "abc" dikonversi ke NaN dulu

// BENAR: gunakan Number.isNaN yang ketat — tidak melakukan konversi
console.log(Number.isNaN("abc")); // ✓ false — "abc" bukan NaN, melainkan string
console.log(Number.isNaN(NaN));   // ✓ true

// batas aman integer
console.log(Number.MAX_SAFE_INTEGER); // 9_007_199_254_740_991 (2^53 - 1)
console.log(Number.MIN_SAFE_INTEGER); // -9_007_199_254_740_991

// angka di luar batas aman kehilangan presisi
console.log(Number.MAX_SAFE_INTEGER + 1 === Number.MAX_SAFE_INTEGER + 2); // true — keduanya sama!

// cek apakah integer aman
console.log(Number.isSafeInteger(42));                        // true
console.log(Number.isSafeInteger(Number.MAX_SAFE_INTEGER));   // true
console.log(Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1)); // false

Konstanta Math #

Objek Math menyediakan konstanta matematika yang sering dibutuhkan, semua dalam presisi floating-point penuh.

console.log(Math.PI);      // 3.141592653589793 — π
console.log(Math.E);       // 2.718281828459045 — bilangan Euler
console.log(Math.SQRT2);   // 1.4142135623730951 — akar kuadrat 2
console.log(Math.SQRT1_2); // 0.7071067811865476 — akar kuadrat 1/2
console.log(Math.LN2);     // 0.6931471805599453 — logaritma natural 2
console.log(Math.LN10);    // 2.302585092994046 — logaritma natural 10
console.log(Math.LOG2E);   // 1.4426950408889634 — log basis 2 dari E
console.log(Math.LOG10E);  // 0.4342944819032518 — log basis 10 dari E

// contoh penggunaan: luas lingkaran
function luasLingkaran(jariJari: number): number {
  return Math.PI * jariJari ** 2;
}

// contoh penggunaan: keliling lingkaran
function kelilingLingkaran(jariJari: number): number {
  return 2 * Math.PI * jariJari;
}

console.log(luasLingkaran(5).toFixed(4));     // "78.5398"
console.log(kelilingLingkaran(5).toFixed(4)); // "31.4159"

Pembulatan #

Pembulatan adalah operasi yang paling sering salah diterapkan. JavaScript menyediakan empat fungsi pembulatan dengan perilaku yang berbeda — penting memilih yang tepat untuk konteks yang benar.

// Math.round — bulatkan ke integer terdekat (0.5 ke atas)
console.log(Math.round(4.4));  // 4
console.log(Math.round(4.5));  // 5
console.log(Math.round(4.6));  // 5
console.log(Math.round(-4.5)); // -4 (bukan -5 — selalu ke atas, bukan ke nol)

// Math.floor — bulatkan ke bawah (menuju -∞)
console.log(Math.floor(4.9));  // 4
console.log(Math.floor(4.1));  // 4
console.log(Math.floor(-4.1)); // -5 (ke bawah, bukan ke nol)

// Math.ceil — bulatkan ke atas (menuju +∞)
console.log(Math.ceil(4.1));   // 5
console.log(Math.ceil(4.9));   // 5
console.log(Math.ceil(-4.9));  // -4 (ke atas, bukan ke nol)

// Math.trunc — potong desimal (menuju 0)
console.log(Math.trunc(4.9));  // 4
console.log(Math.trunc(-4.9)); // -4 (ke nol, bukan ke bawah)
console.log(Math.trunc(4.1));  // 4

// perbandingan Math.floor vs Math.trunc untuk negatif
console.log(Math.floor(-4.1)); // -5 ← lebih kecil
console.log(Math.trunc(-4.1)); // -4 ← mendekati nol

Tabel perbandingan untuk memilih fungsi pembulatan yang tepat:

Nilairoundfloorceiltrunc
4.14454
4.55454
4.95454
-4.1-4-5-4-4
-4.5-4-5-4-4
-4.9-5-5-4-4

Pembulatan ke N Desimal #

Math.round hanya membulatkan ke integer. Untuk membulatkan ke N desimal, ada beberapa pendekatan:

// ANTI-PATTERN: gunakan toFixed() dan konversi kembali ke number
function bulatkanSalah(angka: number, desimal: number): number {
  return parseFloat(angka.toFixed(desimal)); // ✗ masih bisa ada kesalahan floating point
}

// pendekatan yang lebih akurat dengan faktor pengali
function bulatkan(angka: number, desimal: number): number {
  const faktor = 10 ** desimal;
  return Math.round((angka + Number.EPSILON) * faktor) / faktor;
}

console.log(bulatkan(1.005, 2)); // 1.01 — benar
console.log(bulatkan(1.555, 2)); // 1.56 — benar
console.log(bulatkan(3.14159, 3)); // 3.142

// bulatkan ke bawah dengan N desimal
function bulatkanBawah(angka: number, desimal: number): number {
  const faktor = 10 ** desimal;
  return Math.floor(angka * faktor) / faktor;
}

// bulatkan ke atas dengan N desimal
function bulatkanAtas(angka: number, desimal: number): number {
  const faktor = 10 ** desimal;
  return Math.ceil(angka * faktor) / faktor;
}

console.log(bulatkanBawah(1.559, 2)); // 1.55
console.log(bulatkanAtas(1.551, 2));  // 1.56

Nilai Ekstrem dan Perbandingan #

// Math.max dan Math.min — nilai terbesar dan terkecil dari argumen
console.log(Math.max(1, 5, 3, 9, 2)); // 9
console.log(Math.min(1, 5, 3, 9, 2)); // 1

// dengan array — gunakan spread operator
const angka = [3, 1, 4, 1, 5, 9, 2, 6];
console.log(Math.max(...angka)); // 9
console.log(Math.min(...angka)); // 1

// ANTI-PATTERN: spread array besar ke Math.max
// untuk array dengan ribuan elemen, spread bisa menyebabkan stack overflow
const arrayBesar = Array.from({ length: 100_000 }, (_, i) => i);
// Math.max(...arrayBesar) ✗ — bisa crash dengan "Maximum call stack size exceeded"

// BENAR: gunakan reduce untuk array besar
const maksimal = arrayBesar.reduce((maks, val) => (val > maks ? val : maks), -Infinity);
const minimal = arrayBesar.reduce((min, val) => (val < min ? val : min), Infinity);

// Math.abs — nilai absolut
console.log(Math.abs(-42));   // 42
console.log(Math.abs(42));    // 42
console.log(Math.abs(-3.14)); // 3.14

// Math.sign — tanda angka: -1, 0, atau 1
console.log(Math.sign(-5));   // -1
console.log(Math.sign(0));    //  0
console.log(Math.sign(5));    //  1

// Math.clamp — batasi nilai dalam rentang (tidak ada bawaan, buat sendiri)
function clamp(nilai: number, min: number, max: number): number {
  return Math.min(Math.max(nilai, min), max);
}

console.log(clamp(150, 0, 100)); // 100 — terlalu besar, dipotong jadi max
console.log(clamp(-10, 0, 100)); // 0   — terlalu kecil, dipotong jadi min
console.log(clamp(50, 0, 100));  // 50  — dalam rentang, tidak berubah

// use case: clamp nilai slider atau progress bar
const progress = clamp(volume, 0, 100);
const volume = 150;

Pangkat, Akar, dan Logaritma #

// Math.pow — pangkat (alternatif: operator **)
console.log(Math.pow(2, 10));  // 1024
console.log(2 ** 10);          // 1024 — lebih ringkas

// Math.sqrt — akar kuadrat
console.log(Math.sqrt(16));    // 4
console.log(Math.sqrt(2));     // 1.4142135623730951

// Math.cbrt — akar pangkat tiga
console.log(Math.cbrt(27));    // 3
console.log(Math.cbrt(-8));    // -2

// akar pangkat N — gunakan pangkat pecahan
function akarPangkat(angka: number, pangkat: number): number {
  return angka ** (1 / pangkat);
}

console.log(akarPangkat(16, 4));  // 2 (akar pangkat 4 dari 16)
console.log(akarPangkat(32, 5));  // 2 (akar pangkat 5 dari 32)

// Math.log — logaritma natural (basis e)
console.log(Math.log(Math.E));    // 1
console.log(Math.log(1));         // 0
console.log(Math.log(Math.E ** 3)); // 3

// Math.log2 — logaritma basis 2
console.log(Math.log2(1024));     // 10
console.log(Math.log2(256));      // 8

// Math.log10 — logaritma basis 10
console.log(Math.log10(1000));    // 3
console.log(Math.log10(100));     // 2

// logaritma basis N
function logN(angka: number, basis: number): number {
  return Math.log(angka) / Math.log(basis);
}

console.log(logN(81, 3));  // 4 (log basis 3 dari 81)
console.log(logN(625, 5)); // 4 (log basis 5 dari 625)

// Math.hypot — panjang hipotenusa (akar dari jumlah kuadrat)
console.log(Math.hypot(3, 4));    // 5 (teorema Pythagoras: √(9+16))
console.log(Math.hypot(5, 12));   // 13

// jarak antara dua titik 2D
function jarak2D(x1: number, y1: number, x2: number, y2: number): number {
  return Math.hypot(x2 - x1, y2 - y1);
}

// jarak antara dua titik 3D
function jarak3D(
  x1: number, y1: number, z1: number,
  x2: number, y2: number, z2: number
): number {
  return Math.hypot(x2 - x1, y2 - y1, z2 - z1);
}

console.log(jarak2D(0, 0, 3, 4));  // 5
console.log(jarak3D(0, 0, 0, 1, 2, 2)); // 3

Trigonometri #

Semua fungsi trigonometri di Math menggunakan radian, bukan derajat. Ini sumber kesalahan yang sangat umum.

// konversi derajat ↔ radian
function keRadian(derajat: number): number {
  return (derajat * Math.PI) / 180;
}

function keDerajat(radian: number): number {
  return (radian * 180) / Math.PI;
}

// ANTI-PATTERN: langsung pakai derajat tanpa konversi
console.log(Math.sin(90));  // ✗ 0.894 — bukan 1! karena 90 dianggap radian

// BENAR: konversi ke radian dulu
console.log(Math.sin(keRadian(90)));  // ✓ 1
console.log(Math.cos(keRadian(0)));   // ✓ 1
console.log(Math.cos(keRadian(180))); // ✓ -1 (ada floating point noise kecil)

// fungsi trigonometri dasar
console.log(Math.sin(keRadian(30)));  // 0.5
console.log(Math.cos(keRadian(60)));  // 0.5
console.log(Math.tan(keRadian(45)));  // 1 (mendekati — ada floating point noise)

// fungsi invers
console.log(keDerajat(Math.asin(1)));     // 90
console.log(keDerajat(Math.acos(0.5)));   // 60
console.log(keDerajat(Math.atan(1)));     // 45

// Math.atan2 — sudut dari koordinat (x, y) — lebih berguna dari atan
// mengembalikan sudut dalam radian dari sumbu X positif ke titik (x, y)
console.log(keDerajat(Math.atan2(1, 1)));   // 45
console.log(keDerajat(Math.atan2(1, -1)));  // 135
console.log(keDerajat(Math.atan2(-1, -1))); // -135

// contoh: arah angin dari komponen U dan V
function arahAngin(u: number, v: number): number {
  const arahRad = Math.atan2(v, u);
  const arahDerajat = keDerajat(arahRad);
  return (arahDerajat + 360) % 360; // normalisasi ke 0–360
}

// contoh: posisi titik pada lingkaran
function titikPadaLingkaran(
  pusatX: number,
  pusatY: number,
  jariJari: number,
  sudutDerajat: number
): { x: number; y: number } {
  const sudutRad = keRadian(sudutDerajat);
  return {
    x: pusatX + jariJari * Math.cos(sudutRad),
    y: pusatY + jariJari * Math.sin(sudutRad),
  };
}

// titik pada jam 3 (0°), 12 (90°), 9 (180°), 6 (270°) pada jam analog
console.log(titikPadaLingkaran(0, 0, 10, 0));   // { x: 10, y: 0 }
console.log(titikPadaLingkaran(0, 0, 10, 90));  // { x: ~0, y: 10 }

Bilangan Acak #

Math.random() menghasilkan angka acak antara 0 (inklusif) hingga 1 (eksklusif). Dari sini bisa dibangun berbagai generator acak.

// Math.random() — [0, 1)
console.log(Math.random()); // misal: 0.7234...

// integer acak dalam rentang [min, max] inklusif
function acakInt(min: number, max: number): number {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

console.log(acakInt(1, 6));    // dadu: 1–6
console.log(acakInt(0, 100));  // persentase acak

// float acak dalam rentang [min, max)
function acakFloat(min: number, max: number): number {
  return Math.random() * (max - min) + min;
}

console.log(acakFloat(1.0, 5.0)); // misal: 3.4521...

// pilih elemen acak dari array
function pilihanAcak<T>(arr: T[]): T {
  if (arr.length === 0) throw new Error("Array tidak boleh kosong");
  return arr[Math.floor(Math.random() * arr.length)];
}

console.log(pilihanAcak(["batu", "gunting", "kertas"]));

// acak beberapa elemen tanpa duplikat
function sampelAcak<T>(arr: T[], jumlah: number): T[] {
  if (jumlah > arr.length) throw new Error("Jumlah sampel melebihi panjang array");
  const salinan = [...arr];
  const hasil: T[] = [];

  for (let i = 0; i < jumlah; i++) {
    const indeksAcak = Math.floor(Math.random() * (salinan.length - i));
    hasil.push(salinan[indeksAcak]);
    // tukar dengan elemen terakhir yang belum dipilih
    [salinan[indeksAcak], salinan[salinan.length - 1 - i]] = [
      salinan[salinan.length - 1 - i],
      salinan[indeksAcak],
    ];
  }

  return hasil;
}

// acak boolean dengan probabilitas tertentu
function acakBool(probabilitas: number = 0.5): boolean {
  return Math.random() < probabilitas;
}

console.log(acakBool(0.3)); // true 30% dari waktu

// kocok array (Fisher-Yates shuffle)
function kocok<T>(arr: T[]): T[] {
  const hasil = [...arr]; // salin agar tidak mutasi original
  for (let i = hasil.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [hasil[i], hasil[j]] = [hasil[j], hasil[i]];
  }
  return hasil;
}

console.log(kocok([1, 2, 3, 4, 5])); // misal: [3, 1, 5, 2, 4]

Math.random() bukan generator acak yang kriptografis — tidak aman untuk token, password, atau OTP. Untuk keperluan keamanan, gunakan crypto.getRandomValues() (browser) atau crypto.randomBytes() / crypto.randomUUID() (Node.js).

import { randomBytes, randomUUID } from "crypto";

// token acak 32 byte dalam hex
const token = randomBytes(32).toString("hex");

// UUID v4
const id = randomUUID(); // "550e8400-e29b-41d4-a716-446655440000"

Masalah Floating Point #

Floating point adalah sumber bug yang paling sering mengejutkan developer yang baru dari bahasa lain. Memahami karakteristiknya adalah kewajiban, terutama saat bekerja dengan nilai uang.

// masalah presisi floating point yang terkenal
console.log(0.1 + 0.2);           // 0.30000000000000004 — bukan 0.3!
console.log(0.1 + 0.2 === 0.3);   // false

// ANTI-PATTERN: bandingkan floating point secara langsung
if (0.1 + 0.2 === 0.3) {  // ✗ tidak pernah true karena floating point
  console.log("sama");
}

// BENAR: bandingkan dengan toleransi (epsilon)
function hampirSama(a: number, b: number, epsilon = Number.EPSILON): boolean {
  return Math.abs(a - b) < epsilon;
}

console.log(hampirSama(0.1 + 0.2, 0.3)); // true

// untuk keperluan umum, toleransi relative lebih aman
function hampirSamaRelatif(
  a: number,
  b: number,
  toleransi = 1e-9
): boolean {
  if (a === b) return true; // tangani kasus 0 === 0
  return Math.abs(a - b) / Math.max(Math.abs(a), Math.abs(b)) < toleransi;
}

Kalkulasi Uang — Jangan Pakai Float #

// ANTI-PATTERN: kalkulasi harga dengan float
function hitungTotalSalah(hargaSatuan: number, qty: number, diskon: number): number {
  const subtotal = hargaSatuan * qty;
  const nilaiDiskon = subtotal * (diskon / 100);
  return subtotal - nilaiDiskon; // ✗ hasil bisa seperti 89999.99999999999
}

// BENAR: gunakan integer (sen/rupiah terkecil) untuk kalkulasi uang
// simpan semua nilai dalam sen (1 rupiah = 100 sen) lalu konversi di akhir
class Uang {
  private sen: bigint; // gunakan BigInt untuk menghindari overflow

  constructor(rupiah: number) {
    // kalikan 100 untuk konversi ke sen, bulatkan dulu untuk menghindari noise
    this.sen = BigInt(Math.round(rupiah * 100));
  }

  tambah(lain: Uang): Uang {
    const hasil = new Uang(0);
    hasil.sen = this.sen + lain.sen;
    return hasil;
  }

  kurang(lain: Uang): Uang {
    const hasil = new Uang(0);
    hasil.sen = this.sen - lain.sen;
    return hasil;
  }

  kali(faktor: number): Uang {
    const hasil = new Uang(0);
    hasil.sen = BigInt(Math.round(Number(this.sen) * faktor));
    return hasil;
  }

  toRupiah(): number {
    return Number(this.sen) / 100;
  }

  toFormatIDR(): string {
    return this.toRupiah().toLocaleString("id-ID", {
      style: "currency",
      currency: "IDR",
      minimumFractionDigits: 0,
    });
  }
}

// penggunaan
const harga = new Uang(15000);
const diskon = harga.kali(0.1);  // 10%
const total = harga.kurang(diskon);

console.log(total.toFormatIDR()); // "Rp 13.500"

BigInt — Bilangan Bulat Tanpa Batas #

BigInt adalah tipe data primitif untuk bilangan bulat yang melampaui batas Number.MAX_SAFE_INTEGER. Berbeda dari number, BigInt tidak memiliki presisi floating point sama sekali — setiap integer direpresentasikan secara eksak.

// deklarasi BigInt
const besar = 9_007_199_254_740_991n; // suffix 'n'
const dariFungsi = BigInt("9007199254740991");
const dariNumber = BigInt(42);

// operasi aritmetika — operator sama, tapi harus sesama BigInt
console.log(9_007_199_254_740_991n + 1n); // 9007199254740992n — presisi penuh
console.log(2n ** 64n);  // 18446744073709551616n — jauh melampaui MAX_SAFE_INTEGER
console.log(100n / 3n);  // 33n — pembagian integer (tanpa desimal)

// ANTI-PATTERN: mencampur BigInt dan number
console.log(1n + 1);     // ✗ TypeError: Cannot mix BigInt and other types

// BENAR: konversi eksplisit
console.log(1n + BigInt(1)); // ✓ 2n
console.log(Number(10n));    // ✓ 10 — hati-hati: bisa kehilangan presisi untuk BigInt besar

// perbandingan — bisa dibandingkan dengan number menggunakan == (bukan ===)
console.log(42n == 42);   // true (loose equality)
console.log(42n === 42);  // false (strict equality — tipe berbeda)
console.log(42n > 10);    // true — perbandingan lintas tipe diizinkan

// BigInt tidak mendukung Math.*
// Math.sqrt(4n) ✗ — TypeError

// akar kuadrat BigInt (implementasi manual)
function sqrtBigInt(n: bigint): bigint {
  if (n < 0n) throw new Error("Tidak bisa akar kuadrat bilangan negatif");
  if (n < 2n) return n;

  let x = n;
  let y = (x + 1n) / 2n;
  while (y < x) {
    x = y;
    y = (x + n / x) / 2n;
  }
  return x;
}

console.log(sqrtBigInt(144n)); // 12n
console.log(sqrtBigInt(2n ** 128n)); // 18446744073709551616n

// use case: ID unik dari timestamp + random (lebih dari MAX_SAFE_INTEGER)
function generateIdBesar(): bigint {
  const waktu = BigInt(Date.now()); // millisecond timestamp
  const acak = BigInt(Math.floor(Math.random() * 1_000_000));
  return waktu * 1_000_000n + acak;
}

Pola Kalkulasi di Aplikasi Nyata #

Statistik Dasar #

function statistik(data: number[]): {
  min: number;
  max: number;
  jumlah: number;
  rerata: number;
  median: number;
  varians: number;
  stdDeviasi: number;
} {
  if (data.length === 0) throw new Error("Data tidak boleh kosong");

  const terurut = [...data].sort((a, b) => a - b);
  const n = data.length;
  const jumlah = data.reduce((acc, val) => acc + val, 0);
  const rerata = jumlah / n;

  // median
  const tengah = Math.floor(n / 2);
  const median =
    n % 2 === 0
      ? (terurut[tengah - 1] + terurut[tengah]) / 2
      : terurut[tengah];

  // varians populasi
  const varians =
    data.reduce((acc, val) => acc + (val - rerata) ** 2, 0) / n;

  return {
    min: terurut[0],
    max: terurut[n - 1],
    jumlah,
    rerata: bulatkan(rerata, 4),
    median,
    varians: bulatkan(varians, 4),
    stdDeviasi: bulatkan(Math.sqrt(varians), 4),
  };
}

function bulatkan(angka: number, desimal: number): number {
  const faktor = 10 ** desimal;
  return Math.round((angka + Number.EPSILON) * faktor) / faktor;
}

const nilaiUjian = [75, 88, 92, 68, 95, 78, 85, 90, 72, 88];
console.log(statistik(nilaiUjian));
// { min: 68, max: 95, jumlah: 831, rerata: 83.1, median: 86.5, ... }

Normalisasi dan Skala #

// normalisasi min-max — skala nilai ke rentang [0, 1]
function normalisasiMinMax(data: number[]): number[] {
  const min = Math.min(...data);
  const max = Math.max(...data);
  const rentang = max - min;

  if (rentang === 0) return data.map(() => 0); // semua nilai sama

  return data.map((val) => (val - min) / rentang);
}

// normalisasi ke rentang tertentu [targetMin, targetMax]
function normalisasiKe(
  data: number[],
  targetMin: number,
  targetMax: number
): number[] {
  const normalized = normalisasiMinMax(data);
  const targetRentang = targetMax - targetMin;
  return normalized.map((val) => val * targetRentang + targetMin);
}

// interpolasi linear — nilai antara dua titik
function lerp(mulai: number, akhir: number, t: number): number {
  return mulai + (akhir - mulai) * clamp(t, 0, 1);
}

function clamp(nilai: number, min: number, max: number): number {
  return Math.min(Math.max(nilai, min), max);
}

console.log(lerp(0, 100, 0.25));  // 25
console.log(lerp(0, 100, 0.75));  // 75
console.log(lerp(10, 20, 0.5));   // 15

// persentase perubahan
function persentasePerubahan(lama: number, baru: number): number {
  if (lama === 0) return baru === 0 ? 0 : Infinity;
  return bulatkan(((baru - lama) / Math.abs(lama)) * 100, 2);
}

console.log(persentasePerubahan(100, 120)); // 20
console.log(persentasePerubahan(100, 80));  // -20
console.log(persentasePerubahan(100, 100)); // 0

Jarak Geografis — Haversine Formula #

// hitung jarak antara dua koordinat GPS dalam kilometer
function jarakHaversine(
  lat1: number, lon1: number,
  lat2: number, lon2: number
): number {
  const R = 6371; // jari-jari bumi dalam kilometer

  const dLat = keRadian(lat2 - lat1);
  const dLon = keRadian(lon2 - lon1);

  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(keRadian(lat1)) *
    Math.cos(keRadian(lat2)) *
    Math.sin(dLon / 2) ** 2;

  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

  return bulatkan(R * c, 3); // dalam kilometer
}

function keRadian(derajat: number): number {
  return (derajat * Math.PI) / 180;
}

// contoh: jarak Jakarta ke Surabaya
const jarakJktSby = jarakHaversine(
  -6.2088, 106.8456,  // Jakarta
  -7.2575, 112.7521   // Surabaya
);
console.log(`Jarak Jakarta–Surabaya: ${jarakJktSby} km`); // ~664 km

Ringkasan #

  • Semua angka di TypeScript adalah IEEE 754 double-precision — integer dan desimal menggunakan representasi yang sama, sehingga presisi terbatas pada 15–17 digit signifikan.
  • Gunakan Number.isNaN() bukan isNaN() — fungsi global isNaN() melakukan konversi tipe terlebih dahulu yang bisa menghasilkan hasil yang tidak terduga.
  • Math.trunc() berbeda dari Math.floor() untuk bilangan negatiftrunc(-4.9) menghasilkan -4, sementara floor(-4.9) menghasilkan -5.
  • Tambahkan Number.EPSILON saat membulatkan ke N desimal — mencegah hasil yang salah akibat floating point noise seperti 1.005 yang direpresentasikan sebagai 1.00499....
  • Jangan pakai Math.max(...arrayBesar) — spread array dengan lebih dari ~100.000 elemen bisa menyebabkan stack overflow; gunakan reduce sebagai gantinya.
  • Semua fungsi trigonometri menggunakan radian — selalu konversi derajat ke radian dengan (derajat * Math.PI) / 180 sebelum memanggil sin, cos, atau tan.
  • Math.random() tidak kriptografis — untuk token, password, atau OTP, gunakan crypto.randomBytes() atau crypto.randomUUID() dari modul crypto Node.js.
  • Jangan kalkulasi uang dengan float — simpan nilai dalam satuan terkecil (sen) sebagai integer atau gunakan BigInt, lalu konversi ke format tampilan di akhir.
  • BigInt untuk bilangan di luar Number.MAX_SAFE_INTEGER — jangan campur BigInt dan number dalam satu operasi tanpa konversi eksplisit; keduanya tidak kompatibel secara langsung.

← Sebelumnya: I/O   Berikutnya: Crypto →

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