Harga 4.500,50 rupiah terlihat sederhana, tetapi di balik layar komputer menyimpan pecahan dengan dua cara yang sangat berbeda: eksak sebagai digit desimal, atau perkiraan sebagai pecahan biner. Pilihan ini menentukan apakah total belanja pelanggan kita cocok dengan kalkulator kasir atau melenceng beberapa rupiah. Pada Chapter ini kita akan memakai DECIMAL(M,D) untuk nilai yang harus persis, membuktikan lewat SELECT bagaimana FLOAT/DOUBLE bisa menghasilkan 0,30000000000000004, lalu menyusun aturan main kapan memakai tipe yang mana.
DECIMAL: Pecahan yang Persis #
DECIMAL(M,D) menyimpan angka sebagai digit eksak: M adalah total digit maksimal dan D adalah jumlah digit di belakang koma. Sebagai contoh, DECIMAL(10,2) menampung angka hingga 99.999.999,99 — delapan digit di depan koma dan dua digit di belakangnya. Setiap nilai disimpan digit demi digit sehingga 4500.50 dibaca kembali sebagai 4500.50, tanpa pembulatan tersembunyi. Kita buat tabel latihan untuk melihatnya langsung:
DROP DATABASE IF EXISTS belajar_tipe;
CREATE DATABASE belajar_tipe;
USE belajar_tipe;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changedCREATE TABLE produk (
id SMALLINT UNSIGNED,
nama VARCHAR(50),
harga DECIMAL(10,2),
berat_kg DOUBLE
);
DESC produk;Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
+----------+-------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------------+------+-----+---------+-------+
| id | smallint unsigned | YES | | NULL | |
| nama | varchar(50) | YES | | NULL | |
| harga | decimal(10,2) | YES | | NULL | |
| berat_kg | double | YES | | NULL | |
+----------+-------------------+------+-----+---------+-------+
4 rows in set (0.00 sec)Definisi kolom harga DECIMAL(10,2) muncul utuh pada kolom Type hasil DESC — MySQL menyimpan parameter presisi ini sebagai bagian dari tipe, berbeda dengan INT(11) yang angkanya dibuang. Kolom berat_kg DOUBLE sengaja kita campurkan untuk pembanding di bagian berikutnya. Sekarang kita isi datanya dan menghitung pajak 11%:
INSERT INTO produk (id, nama, harga, berat_kg) VALUES
(1, 'Pensil Kayu', 2500.00, 0.005),
(2, 'Buku Tulis 38', 4500.50, 0.21),
(3, 'Amat Tulis', 12500.75, 0.11);
SELECT id, nama, harga, harga * 0.11 AS pajak, harga * 1.11 AS total_bayar
FROM produk
ORDER BY id;Output yang Dihasilkan:
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
+------+---------------+----------+-----------+-------------+
| id | nama | harga | pajak | total_bayar |
+------+---------------+----------+-----------+-------------+
| 1 | Pensil Kayu | 2500.00 | 275.0000 | 2775.0000 |
| 2 | Buku Tulis 38 | 4500.50 | 495.0550 | 4995.5550 |
| 3 | Amat Tulis | 12500.75 | 1375.0825 | 13875.8325 |
+------+---------------+----------+-----------+-------------+
3 rows in set (0.00 sec)Perhatikan kolom pajak dan total_bayar: hasil 275.0000 dan 2775.0000 dihitung dari nilai DECIMAL secara eksak, digit demi digit — persis seperti kalau kita mengalikan dengan kalkulator. Perkalian antar nilai DECIMAL mempertahankan sifat eksaknya, sehingga menjumlahkan ratusan baris faktur pun tidak menimbulkan selisih pembulatan yang menggumpal. Batas presisinya sendiri dijaga ketat oleh server:
-- @error
INSERT INTO produk (id, nama, harga) VALUES (4, 'Kaleng Cat', 123456789.99);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
ERROR 1264 (22003): Out of range value for column 'harga' at row 1
ByeNilai 123456789.99 memiliki 11 digit sementara DECIMAL(10,2) hanya menampung 10 digit total, sehingga server menolaknya dengan galat out-of-range — sama kerasnya dengan kasus integer pada chapter sebelumnya. Bila aplikasi kita memang menangani nominal miliaran dengan dua desimal, deklarasikan DECIMAL(13,2) sejak awal; mengubah lebar kolom setelah tabel terisi jutaan baris jauh lebih menyusahkan.
FLOAT dan DOUBLE: Perkiraan Biner #
FLOAT dan DOUBLE menyimpan pecahan dalam format biner floating-point (standar IEEE 754). Masalahnya, angka desimal sederhana seperti 0,1 tidak memiliki representasi biner yang persis — sama seperti 1/3 tidak bisa ditulis persis dalam desimal. Selisihnya kecil, tetapi muncul ke permukaan saat angka dijumlahkan atau dibandingkan. Kita bisa membuktikannya dengan notasi e0 yang memaksa MySQL memperlakukan literal sebagai bilangan biner:
SELECT 0.1e0 + 0.2e0 AS hasil_biner, 0.1 + 0.2 AS hasil_desimal;
SELECT 0.1e0 + 0.2e0 = 0.3e0 AS biner_sama, 0.1 + 0.2 = 0.3 AS desimal_sama;Output yang Dihasilkan:
+---------------------+---------------+
| hasil_biner | hasil_desimal |
+---------------------+---------------+
| 0.30000000000000004 | 0.3 |
+---------------------+---------------+
1 row in set (0.00 sec)
+------------+--------------+
| biner_sama | desimal_sama |
+------------+--------------+
| 0 | 1 |
+------------+--------------+
1 row in set (0.00 sec)Hasil blok tersebut adalah inti chapter ini. Penjumlahan biner 0.1e0 + 0.2e0 menghasilkan 0.30000000000000004 — sedikit lebih besar dari 0,3 — sehingga pembanding = 0.3e0 mengembalikan 0 (tidak sama). Sebaliknya, literal 0.1 dan 0.2 tanpa e0 diperlakukan MySQL sebagai DECIMAL, dijumlahkan secara eksak menjadi 0.3, dan pembandingnya mengembalikan 1. Selisih sepersekian triliun ini terasa sepele, tetapi bayangkan perhitungan diskon, pajak, dan pembagian tagihan yang diulang ribuan kali — galat kecil bisa terakumulasi menjadi selisih yang membuat rekonsiliasi keuangan gagal.
Kedua tipe biner punya kapasitas yang berbeda: FLOAT memakai 4 byte dengan sekitar 7 digit signifikan, sedangkan DOUBLE memakai 8 byte dengan sekitar 15 digit signifikan. Kita bisa melihat kehilangan presisi FLOAT pada angka enam digit:
SELECT CAST(12345.678 AS FLOAT) AS presisi_float, CAST(12345.678 AS DOUBLE) AS presisi_double;Output yang Dihasilkan:
+---------------+----------------+
| presisi_float | presisi_double |
+---------------+----------------+
| 12345.7 | 12345.678 |
+---------------+----------------+
1 row in set (0.00 sec)Nilai 12345.678 dipangkas menjadi 12345.7 ketika dipaksa masuk ke FLOAT — hanya enam digit pertama yang selamat — sementara DOUBLE menyimpan ketujuh digitnya utuh. Untuk data ilmiah dan statistik, DOUBLE hampir selalu lebih aman daripada FLOAT karena ruang ekstra 4 byte itu murah dibanding risiko kehilangan digit.
Kapan Memakai yang Mana #
Keluarga pecahan ini punya wilayah kerja yang jelas. Tabel berikut merangkum posisi masing-masing:
| Aspek | DECIMAL(M,D) | FLOAT | DOUBLE |
|---|---|---|---|
| Cara simpan | Digit desimal eksak | Pecahan biner perkiraan | Pecahan biner perkiraan |
| Presisi | Persis sesuai M dan D | ± 7 digit signifikan | ± 15 digit signifikan |
| Ukuran tetap | Variabel sesuai jumlah digit | 4 byte | 8 byte |
| Contoh pemakaian | Harga, saldo, pajak, kurs tetap | Skor latihan, koordinat kasar | Koordinat GPS, hasil pengukuran |
| Perhitungan | Eksak, aman dijumlahkan | Bisa melenceng | Bisa melenceng |
Aturan praktisnya bisa kita padatkan menjadi tiga kalimat. Semua yang berkaitan dengan uang memakai DECIMAL — tanpa kecuali. Pengukuran fisik yang memang tidak pernah eksak (berat, jarak, suhu, koordinat) cukup memakai DOUBLE. FLOAT hanya dipakai bila ruang penyimpanan benar-benar kritis dan presisi 7 digit memang sudah lebih dari cukup — misalnya jutaan baris data sensor.
Terakhir, kita bereskan database latihan:
DROP DATABASE belajar_tipe;Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)Referensi #
- Fixed-Point Types (Exact Value) — DECIMAL, NUMERIC — MySQL 8.4 Reference Manual
- Floating-Point Types (Approximate Value) — FLOAT, DOUBLE — MySQL 8.4 Reference Manual
- Numeric Data Types — MySQL 8.4 Reference Manual
Angka sudah beres — mulai dari bulat sampai pecahan persis. Data berikutnya yang paling sering kita simpan justru berupa teks: nama pelanggan, alamat, sampai deskripsi produk. Chapter berikutnya membedah CHAR, VARCHAR, keluarga TEXT, dan ENUM untuk memilih wadah teks yang tepat.