Chapter sebelumnya menutup dengan misteri: mengapa double precision menjumlahkan rupiah menghasilkan 600000.4500000001 alih-alih 600000.45? Jawabannya bukan bug PostgreSQL — ia sifat dasar floating point, format bilangan yang dipakai semua komputer modern untuk pecahan. Format ini cepat dan ringkas, sehingga tetap jadi tulang punggung komputasi ilmiah, grafik, dan sensor. Tetapi cara kerjanya menyimpan angka dalam biner membuat sebagian desimal — termasuk 0.1 — mustahil disimpan persis, seperti mustahilnya menulis sepertiga dalam desimal tanpa pengulangan. Chapter ini membedah mekanisme itu dengan bukti-bukti langsung dari server, membedakan real dari double precision, dan menutup dengan panduan kapan float aman dipakai.
Dua Ukuran Floating Point #
PostgreSQL mengikuti standar IEEE 754 dengan dua ukuran tipe floating point:
| Tipe | Penyimpanan | Presisi desimal | Rentang nilai |
|---|---|---|---|
real | 4 byte | ±6 digit | sekitar 1.18e-38 sampai 3.40e+38 |
double precision | 8 byte | ±15 digit | sekitar 2.23e-308 sampai 1.80e+308 |
Angka “presisi ±6 digit” berarti real hanya dapat diandalkan sampai enam digit signifikan — angka ketujuh dan seterusnya sudah bisa melenceng. Rentangnya tampak raksasa (hingga 10 pangkat 38), tetapi presisi yang sempit itulah harga yang dibayar: hanya 32 bit tersedia, dibagi antara tanda, pangkat, dan mantisa.
Mengapa 0.1 Tidak Pernah Tepat #
Floating point menyimpan pecahan sebagai penjumlahan pangkat dua: setengah, seperempat, seperdelapan, dan seterusnya. Desimal 0.1 sama dengan biner 0.0001100110011... yang berulang tanpa henti, sehingga harus dipotong pada suatu titik — dan hasil potongannya selalu sedikit lebih besar atau sedikit lebih kecil dari 0.1 sejati. Kita buktikan:
SELECT 0.1::float8 + 0.2::float8 AS jumlah;
SELECT 0.1::float8 + 0.2::float8 - 0.3::float8 AS selisih;
SELECT 0.1::float8 + 0.2::float8 = 0.3::float8 AS sama_dengan_03;Output yang Dihasilkan:
jumlah
---------------------
0.30000000000000004
(1 row)
selisih
-----------------------
5.551115123125783e-17
(1 row)
sama_dengan_03
----------------
f
(1 row)Tiga hasil itu menceritakan satu kisah. Query pertama: 0.1 dan 0.2 masing-masing sudah disimpan sebagai pendekatan biner, dan jumlah keduanya tampil sebagai 0.30000000000000004 — bukan 0.3. Query kedua mengukur besarnya kekeliruan itu: selisih terhadap 0.3 sejati adalah 5.55e-17, satu satuan di urutan ke-17. Query ketiga menegaskan konsekuensinya: perbandingan = mengembalikan f, karena nilai yang tersimpan memang berbeda bit demi bit.
Perhatikan juga bahwa psql tidak menampilkan seluruh digit biner yang tersimpan — 0.1::float8 tampil rapi sebagai 0.1 karena tampilan default PostgreSQL memakai representasi desimal terpendek yang masih menghasilkan nilai biner yang sama ketika dibaca ulang. Kedalaman galat baru terlihat begitu nilainya dijumlahkan atau dibandingkan, seperti pada dua query pertama.
Presisi real Hanya ±6 Digit #
Kalau double precision masih sanggup menyimpan 123456789 utuh, real sudah tidak sanggup. Karena hanya punya ±6 digit presisi, real membulatkan lebih agresif — dan pelebaran cast ke double precision (yang dilakukan tanpa kehilangan apa pun) membuat pembulatan itu terlihat telanjang:
SELECT 36.6::real AS suhu_real,
36.6::real::float8 AS nilai_yang_disimpan;
SELECT 123456789::real AS real_hanya_tujuh_digit,
123456789::float8 AS double_presisi;
CREATE TABLE sensor (
id integer PRIMARY KEY,
nama text NOT NULL,
suhu real NOT NULL
);
INSERT INTO sensor (id, nama, suhu) VALUES
(1, 'Ruang Server', 36.6),
(2, 'Gudang', 24.1);
SELECT id, nama, suhu,
suhu::float8 AS nilai_sebenarnya
FROM sensor
ORDER BY id;Output yang Dihasilkan:
suhu_real | nilai_yang_disimpan
-----------+---------------------
36.6 | 36.599998474121094
(1 row)
real_hanya_tujuh_digit | double_presisi
------------------------+----------------
1.2345679e+08 | 123456789
(1 row)
CREATE TABLE
INSERT 0 2
id | nama | suhu | nilai_sebenarnya
----+--------------+------+--------------------
1 | Ruang Server | 36.6 | 36.599998474121094
2 | Gudang | 24.1 | 24.100000381469727
(2 rows)Mari kita bedah hasilnya:
- Query pertama:
36.6disimpan sebagaireallalu dilebarkan kedouble precisionmemperlihatkan nilai yang benar-benar tersimpan:36.599998474121094. Kolomsuhu_realmenampilkan36.6karena pembulatan tampilan, tetapi isi memorinya bukan36.6— dan itulah yang dipakai saat berhitung. - Query kedua:
123456789(sembilan digit) melampaui kapasitas ±6 digitreal, sehingga tersimpan sebagai1.2345679e+08— nilai persisnya123456792, tiga digit terakhir dari bilangan asli sudah tidak dapat dipertahankan.double precisiondengan ±15 digit menampungnya persis. - Tabel
sensor: pola nyata dari aplikasi IoT — pembacaan suhu disimpan sebagairealkarena presisi seperseribu derajat pun tidak dibutuhkan alat ukur, dan 4 byte per nilai menghemat setengah penyimpanan dibandingdouble precision. Kolomnilai_sebenarnyakembali memperlihatkan wajah asli angka-angka itu di balik tampilan manisnya.
Nama Alias #
Floating point punya banyak nama karena warisan berbagai standar. PostgreSQL menerimanya semua, dan seluruh alias mengarah ke tipe yang sama:
| Alias | Tipe sebenarnya |
|---|---|
float4 | real |
real | real |
float8 | double precision |
double precision | double precision |
Satu kasus khusus: float tanpa nomor mengikuti standar SQL dan menjadi double precision — kecuali saat dipakai dalam CAST(x AS float(n)), yang memilih real untuk n sampai 24 dan double precision untuk n di atasnya. Kita periksa langsung lewat fungsi pg_typeof:
SELECT pg_typeof(0.5::real) AS tipe_real,
pg_typeof(0.5::float4) AS tipe_float4,
pg_typeof(0.5::float) AS tipe_float,
pg_typeof(0.5::double precision) AS tipe_double;
SELECT 0.5::float4 = 0.5::real AS float4_sama_real,
0.5::float = 0.5::double precision AS float_sama_double;Output yang Dihasilkan:
tipe_real | tipe_float4 | tipe_float | tipe_double
-----------+-------------+------------------+------------------
real | real | double precision | double precision
(1 row)
float4_sama_real | float_sama_double
------------------+-------------------
t | t
(1 row)Fungsi pg_typeof melaporkan tipe setiap ekspresi: float4 dan real dijawab real, sedangkan float dan double precision dijawab double precision. Perbandingan kesetaraan di query kedua konsisten dengan laporan itu — nilai-nilai yang bertipe sama dibandingkan tanpa konversi tambahan.
Infinity dan NaN #
Berbeda dengan numeric dan integer, floating point mengenal tiga nilai khusus dari IEEE 754: tak terhingga positif, tak terhingga negatif, dan NaN (not a number) — hasil operasi yang tidak terdefinisi seperti pembagian nol demi nol:
SELECT 'Infinity'::float8 AS tak_terhingga,
'-Infinity'::float8 AS minus_tak_terhingga,
'NaN'::float8 AS bukan_angka;
SELECT 'NaN'::float8 = 'NaN'::float8 AS nan_sama_nan,
'NaN'::float8 > 1000000::float8 AS nan_paling_besar;Output yang Dihasilkan:
tak_terhingga | minus_tak_terhingga | bukan_angka
---------------+---------------------+-------------
Infinity | -Infinity | NaN
(1 row)
nan_sama_nan | nan_paling_besar
--------------+------------------
t | t
(1 row)Perilaku NaN di PostgreSQL mengejutkan mereka yang hafal standar IEEE: menurut IEEE, NaN tidak pernah sama dengan NaN, tetapi perbandingan pertama di atas menghasilkan t. PostgreSQL sengaja memperlakukan NaN sebagai nilai terbesar dari semua angka — perbandingan NaN > 1000000 pun menghasilkan t — supaya ORDER BY dan index selalu punya urutan total. Nilai khusus ini berguna sebagai penanda pembacaan sensor yang gagal, tetapi jangan biarkan NaN diam-diam ikut terjumlah dalam laporan.
Kapan Floating Point Aman Dipakai #
Setelah melihat semua jebakannya, posisi float menjadi jelas:
- Aman: pengukuran fisik (suhu, jarak, berat) yang memang sudah punya galat instrumen; komputasi ilmiah dan statistik yang menerima pembulatan; grafik dan game yang hanya butuh tampilan mulus.
- Tidak aman: uang dan pembukuan (pakai
numeric), dan nilai yang harus bandingkan dengan=secara persis — dua float yang tampak sama bisa berbeda di digit ke-16.
Dengan aturan itu, float menjadi alat yang jujur: cepat, ringkas, dan dapat diprediksi selama kita tahu bahwa isinya pendekatan. Topik tipe data berikutnya berpindah dari angka ke teks — di sana PostgreSQL menawarkan tiga pilihan (char, varchar, text) yang konon berbeda kecepatannya, tapi ternyata menyimpan rahasia yang mengejutkan.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
alias-tipe.sql
Unduh berkasSELECT pg_typeof(0.5::real) AS tipe_real,
pg_typeof(0.5::float4) AS tipe_float4,
pg_typeof(0.5::float) AS tipe_float,
pg_typeof(0.5::double precision) AS tipe_double;
SELECT 0.5::float4 = 0.5::real AS float4_sama_real,
0.5::float = 0.5::double precision AS float_sama_double;
bukti-binari.sql
Unduh berkasSELECT 0.1::float8 + 0.2::float8 AS jumlah;
SELECT 0.1::float8 + 0.2::float8 - 0.3::float8 AS selisih;
SELECT 0.1::float8 + 0.2::float8 = 0.3::float8 AS sama_dengan_03;
nilai-khusus.sql
Unduh berkasSELECT 'Infinity'::float8 AS tak_terhingga,
'-Infinity'::float8 AS minus_tak_terhingga,
'NaN'::float8 AS bukan_angka;
SELECT 'NaN'::float8 = 'NaN'::float8 AS nan_sama_nan,
'NaN'::float8 > 1000000::float8 AS nan_paling_besar;
real-vs-double.sql
Unduh berkasSELECT 36.6::real AS suhu_real,
36.6::real::float8 AS nilai_yang_disimpan;
SELECT 123456789::real AS real_hanya_tujuh_digit,
123456789::float8 AS double_presisi;
CREATE TABLE sensor (
id integer PRIMARY KEY,
nama text NOT NULL,
suhu real NOT NULL
);
INSERT INTO sensor (id, nama, suhu) VALUES
(1, 'Ruang Server', 36.6),
(2, 'Gudang', 24.1);
SELECT id, nama, suhu,
suhu::float8 AS nilai_sebenarnya
FROM sensor
ORDER BY id;