Harga satuan, jumlah stok, selisih kas — angka adalah penghuni tetap kolom database, dan pengolahannya penuh aturan yang tidak terlihat mata: pembulatan yang arahnya berbeda antara numeric dan float, sisa bagi yang berguna untuk pengelompokan, hingga akar dan pangkat untuk rumus perhitungan. Kesalahan kecil di pemilihan fungsi pembulatan bisa berujung pada laporan keuangan yang tidak cocok satu rupiah pun dengan buku besar. Chapter ini memetakan fungsi matematika bawaan PostgreSQL yang paling sering dipakai sehari-hari, sekaligus membuktikan langsung di server latihan perbedaan perilaku pembulatan yang menjadi sumber kejutan paling sering di keluarga fungsi ini.

Nilai Absolut dan Tanda #

Dua fungsi pembuka yang tampak sederhana tetapi sangat praktis untuk data kas: abs() membuang tanda minus, sedangkan sign() meringkas arah sebuah nilai menjadi -1, 0, atau 1. Untuk mendemonstrasikannya, kita pakai tabel mutasi kas kecil milik Toko Maju Jaya yang mencatat pemasukan sebagai angka positif dan pengeluaran sebagai angka negatif:

-- tabel mutasi kas sederhana untuk demo abs() dan sign()
CREATE TABLE mutasi (
    id          integer PRIMARY KEY,
    keterangan  text NOT NULL,
    jumlah      integer NOT NULL
);

INSERT INTO mutasi (id, keterangan, jumlah) VALUES
    (1, 'penjualan tunai', 250000),
    (2, 'pengeluaran belanja kopi', -175000),
    (3, 'pengeluaran sewa kios', -500000),
    (4, 'penjualan kredit', 125000);

SELECT keterangan, jumlah,
       abs(jumlah) AS nilai_absolut,
       sign(jumlah) AS arah
FROM mutasi
ORDER BY id;

-- sign() mengembalikan -1, 0, atau 1
SELECT sign(-7) AS negatif,
       sign(0) AS nol,
       sign(7.5) AS positif;

Output yang Dihasilkan:

text
CREATE TABLE
INSERT 0 4
        keterangan        | jumlah  | nilai_absolut | arah 
--------------------------+---------+---------------+------
 penjualan tunai          |  250000 |        250000 |    1
 pengeluaran belanja kopi | -175000 |        175000 |   -1
 pengeluaran sewa kios    | -500000 |        500000 |   -1
 penjualan kredit         |  125000 |        125000 |    1
(4 rows)

 negatif | nol | positif 
---------+-----+---------
      -1 |   0 |       1
(1 row)

Kolom nilai_absolut menampilkan besaran uang tanpa memandang arah — berguna ketika laporan hanya ingin tahu “berapa besar nilainya”, misalnya untuk menghitung total volume transaksi tanpa membedakan masuk dan keluar. Kolom arah memampatkan informasi yang sama menjadi tiga kemungkinan nilai; sign() menerima angka pecahan apa pun tetapi hasilnya selalu bilangan bulat -1, 0, atau 1, sehingga cocok dipakai sebagai penanda dalam CASE atau filter cepat.

Pembulatan round dan Perbedaan numeric vs float #

Fungsi round() adalah fungsi numerik yang paling sering salah dipahami, karena PostgreSQL memelihara dua perilaku sekaligus tergantung tipe argumennya. Pada numeric, nilai setengah dibulatkan menjauhi nol; pada double precision, pembulatan mengikuti aturan IEEE round half to even — nilai setengah dibulatkan ke bilangan genap terdekat:

EkspresiHasilAturan yang berlaku
round(2.5)3numeric: setengah menjauhi nol
round(-2.5)-3numeric: setengah menjauhi nol
round(2.5::float8)2float: setengah ke genap terdekat
round(0.5::float8)0float: setengah ke genap terdekat
round(3.5::float8)4float: setengah ke genap terdekat
sql pembulatan.sql
-- round() pada numeric: nilai .5 dibulatkan menjauhi nol
SELECT round(2.5) AS positif_setengah,
       round(-2.5) AS negatif_setengah,
       round(2.4) AS di_bawah_setengah;

-- round() dengan jumlah angka desimal
SELECT round(123.456, 2) AS dua_desimal,
       round(123.456, 0) AS tanpa_desimal;

-- round() dengan tempat negatif: pembulatan puluhan dan ratusan
SELECT round(1234, -2) AS ratusan_bawah,
       round(1251, -2) AS ratusan_atas;

-- scale() melaporkan jumlah digit di belakang koma
SELECT scale(12.3450) AS skala_nilai;

Output yang Dihasilkan:

text
 positif_setengah | negatif_setengah | di_bawah_setengah 
------------------+------------------+-------------------
                3 |               -3 |                 2
(1 row)

 dua_desimal | tanpa_desimal 
-------------+---------------
      123.46 |           123
(1 row)

 ratusan_bawah | ratusan_atas 
---------------+--------------
          1200 |         1300
(1 row)

 skala_nilai 
-------------
           4
(1 row)

Tiga perilaku pada contoh di atas layak dicermati:

  1. round(2.5) dan round(-2.5): literal 2.5 tanpa anotasi tipe ditafsirkan sebagai numeric, sehingga aturan “menjauhi nol” berlaku — hasilnya 3 dan -3.
  2. round(1234, -2): tempat negatif membulatkan pada digit ratusan, sehingga 1234 menjadi 1200 dan 1251 melompat ke 1300 — trik yang sering dipakai untuk mengelompokkan nilai harga ke dalam rentang ratusan ribu atau jutaan.
  3. scale(12.3450): mengembalikan 4 karena nilai numeric menyimpan angka nol di belakang koma sebagai bagian dari skalanya. Presisi dan skala tipe numeric(p,s) sudah dibahas tuntas di chapter Numeric dan Decimal .

Sekarang giliran sisi float dari pembulatan, lengkap dengan perilaku spesial NaN (not a number) yang hanya dimiliki tipe floating-point:

-- pembulatan float mengikuti aturan "half to even" (dibulatkan ke angka genap)
SELECT round(2.5::float8) AS float_dua_setengah,
       round(0.5::float8) AS float_nol_koma_lima,
       round(3.5::float8) AS float_tiga_koma_lima;

-- NaN pada float dibandingkan seolah-olah lebih besar dari angka apa pun
SELECT 'NaN'::float8 > 100 AS nan_lebih_besar,
       'NaN'::float8 = 'NaN'::float8 AS nan_sama_dengan_nan;

-- pembulatan NaN tetap menghasilkan NaN
SELECT round('NaN'::float8) AS bulatkan_nan;

Output yang Dihasilkan:

text
 float_dua_setengah | float_nol_koma_lima | float_tiga_koma_lima 
--------------------+---------------------+----------------------
                  2 |                   0 |                    4
(1 row)

 nan_lebih_besar | nan_sama_dengan_nan 
-----------------+---------------------
 t               | t
(1 row)

 bulatkan_nan 
--------------
          NaN
(1 row)

Hasil 2, 0, dan 4 untuk ketiga pembulatan float itu bukan kebetulan: 2.5 jatuh tepat di tengah antara 2 dan 3, dan aturan half to even memilih bilangan genap — 2. Nilai 0.5 dibulatkan ke 0 karena 0 genap, sedangkan 3.5 dibulatkan ke 4 karena 4 genap. Perilaku NaN juga khas PostgreSQL: NaN dianggap lebih besar dari angka biasa apa pun, dan NaN = NaN mengembalikan true — berbeda dari standar IEEE yang menyatakan perbandingan dengan NaN selalu false. Pembulatan NaN pun tidak error, hasilnya tetap NaN.

-- round() dua argumen hanya tersedia untuk numeric, bukan double precision
SELECT round(3.14159::float8, 2) AS gagal;

Output yang Dihasilkan:

text
psql:round-float-error.sql:2: ERROR:  function round(double precision, integer) does not exist
LINE 1: SELECT round(3.14159::float8, 2) AS gagal;
               ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.

Error function round(double precision, integer) does not exist menyatakan persis masalahnya: PostgreSQL mencari fungsi round yang menerima double precision dan integer, dan tidak menemukannya. Solusinya ada dua — ubah argumen menjadi numeric dengan round(3.14159::numeric, 2), atau bulatkan bertahap dengan round(3.14159::float8) lalu atur tampilannya di sisi aplikasi. Pilihan pertama lebih tepat untuk perhitungan uang.

Pembulatan Arah: ceil, floor, dan trunc #

Ketika arah pembulatan tidak boleh diserahkan pada nilai desimalnya, tiga fungsi berikut memberi kendali penuh — ceil() selalu ke atas, floor() selalu ke bawah, dan trunc() memotong tanpa peduli arah:

FungsiArahContohHasil
ceil(x) / ceiling(x)Ke atas (menuju +∞)ceil(3.1)4
floor(x)Ke bawah (menuju -∞)floor(3.9)3
trunc(x)Menuju noltrunc(-3.9)-3
trunc(x, n)Menuju nol, sisakan n desimaltrunc(123.456, 1)123.4
-- ceil()/ceiling() membulatkan ke atas, floor() ke bawah
SELECT ceil(3.1) AS ke_atas,
       ceiling(3.1) AS padanan_ceiling,
       floor(3.9) AS ke_bawah,
       ceil(-3.1) AS negatif_ke_atas,
       floor(-3.1) AS negatif_ke_bawah;

-- trunc() memotong angka desimal tanpa membulatkan
SELECT trunc(3.9) AS potong_positif,
       trunc(-3.9) AS potong_negatif,
       trunc(123.456, 1) AS potong_satu_desimal,
       trunc(123.456, -1) AS potong_puluhan;

Output yang Dihasilkan:

text
 ke_atas | padanan_ceiling | ke_bawah | negatif_ke_atas | negatif_ke_bawah 
---------+-----------------+----------+-----------------+------------------
       4 |               4 |        3 |              -3 |               -4
(1 row)

 potong_positif | potong_negatif | potong_satu_desimal | potong_puluhan 
----------------+----------------+---------------------+----------------
              3 |             -3 |               123.4 |            120
(1 row)

Perbedaan yang paling penting justru muncul pada bilangan negatif: ceil(-3.1) menghasilkan -3 (ke atas berarti menuju nol), floor(-3.1) menghasilkan -4 (ke bawah berarti menjauhi nol), sedangkan trunc(-3.9) menghasilkan -3 karena potongan menuju nol. Dalam praktik bisnis, ceil() dipakai untuk “jumlah kotak pengiriman minimal” (7,2 kotak berarti 8 kotak), floor() untuk kuota yang tidak boleh dilampaui, dan trunc() untuk memotong digit promo tanpa membulatkan. ceiling() hanyalah nama lain dari ceil() — keduanya dijalankan oleh fungsi yang sama.

Sisa Bagi, Pangkat, dan Akar #

Tiga fungsi penutup menutup kebutuhan aritmetika sehari-hari. mod() mengembalikan sisa pembagian dan punya padanan operator %; power() dan sqrt() menangani pangkat dan akar kuadrat:

-- mod() dan operator % menghasilkan sisa pembagian
SELECT mod(10, 3) AS sisa_fungsi,
       10 % 3 AS sisa_operator,
       mod(-10, 3) AS sisa_negatif;

-- power() menghitung pangkat, sqrt() menghitung akar kuadrat
SELECT power(2, 10) AS dua_pangkat_10,
       sqrt(2) AS akar_dua;

-- pangkat negatif menghasilkan pecahan
SELECT power(10, -3) AS sepuluh_pangkat_minus_tiga;

Output yang Dihasilkan:

text
 sisa_fungsi | sisa_operator | sisa_negatif 
-------------+---------------+--------------
           1 |             1 |           -1
(1 row)

 dua_pangkat_10 |      akar_dua      
----------------+--------------------
           1024 | 1.4142135623730951
(1 row)

 sepuluh_pangkat_minus_tiga 
----------------------------
                      0.001
(1 row)

mod() dan % menghasilkan nilai yang sama untuk angka positif; untuk mod(-10, 3) hasilnya -1 — tanda hasil mengikuti tanda bilangan yang dibagi, bukan pembaginya. Fungsi mod() paling sering dipakai untuk pengelompokan berselang — misalnya WHERE id % 2 = 0 untuk mengambil baris genap, atau mod(id, 10) untuk membagi data uji ke sepuluh kelompok. Perhatikan juga bahwa power(10, -3) sah dan menghasilkan pecahan 0.001; pangkat negatif bukan error. Satu operasi yang benar-benar ditolak server adalah akar kuadrat dari bilangan negatif:

-- akar kuadrat dari bilangan negatif tidak ada di bilangan real
SELECT sqrt(-1) AS gagal;

Output yang Dihasilkan:

text
psql:sqrt-negatif.sql:2: ERROR:  cannot take square root of a negative number

Error cannot take square root of a negative number muncul karena hasilnya bukan bilangan real — PostgreSQL tidak otomatis melompat ke bilangan imajiner. Bila data sensor Anda memang bisa bernilai negatif, saring dulu dengan abs() atau filter WHERE nilai >= 0 sebelum memanggil sqrt().

Dengan perangkat aritmetika ini, angka di kolom bisa dibulatkan, dipotong, dan dihitung sesuai aturan bisnis. Namun tanggal dan waktu punya kelas fungsi sendiri — mengambil komponen tahun, memotong ke awal bulan, menghitung usia — yang tidak bisa disentuh fungsi matematika biasa. Chapter berikutnya mengulas Fungsi Date dan Time .

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

abs-dan-sign.sql

Unduh berkas
sql
-- tabel mutasi kas sederhana untuk demo abs() dan sign()
CREATE TABLE mutasi (
    id          integer PRIMARY KEY,
    keterangan  text NOT NULL,
    jumlah      integer NOT NULL
);

INSERT INTO mutasi (id, keterangan, jumlah) VALUES
    (1, 'penjualan tunai', 250000),
    (2, 'pengeluaran belanja kopi', -175000),
    (3, 'pengeluaran sewa kios', -500000),
    (4, 'penjualan kredit', 125000);

SELECT keterangan, jumlah,
       abs(jumlah) AS nilai_absolut,
       sign(jumlah) AS arah
FROM mutasi
ORDER BY id;

-- sign() mengembalikan -1, 0, atau 1
SELECT sign(-7) AS negatif,
       sign(0) AS nol,
       sign(7.5) AS positif;

ceil-floor-trunc.sql

Unduh berkas
sql
-- ceil()/ceiling() membulatkan ke atas, floor() ke bawah
SELECT ceil(3.1) AS ke_atas,
       ceiling(3.1) AS padanan_ceiling,
       floor(3.9) AS ke_bawah,
       ceil(-3.1) AS negatif_ke_atas,
       floor(-3.1) AS negatif_ke_bawah;

-- trunc() memotong angka desimal tanpa membulatkan
SELECT trunc(3.9) AS potong_positif,
       trunc(-3.9) AS potong_negatif,
       trunc(123.456, 1) AS potong_satu_desimal,
       trunc(123.456, -1) AS potong_puluhan;

mod-power-sqrt.sql

Unduh berkas
sql
-- mod() dan operator % menghasilkan sisa pembagian
SELECT mod(10, 3) AS sisa_fungsi,
       10 % 3 AS sisa_operator,
       mod(-10, 3) AS sisa_negatif;

-- power() menghitung pangkat, sqrt() menghitung akar kuadrat
SELECT power(2, 10) AS dua_pangkat_10,
       sqrt(2) AS akar_dua;

-- pangkat negatif menghasilkan pecahan
SELECT power(10, -3) AS sepuluh_pangkat_minus_tiga;

pembulatan-float.sql

Unduh berkas
sql
-- pembulatan float mengikuti aturan "half to even" (dibulatkan ke angka genap)
SELECT round(2.5::float8) AS float_dua_setengah,
       round(0.5::float8) AS float_nol_koma_lima,
       round(3.5::float8) AS float_tiga_koma_lima;

-- NaN pada float dibandingkan seolah-olah lebih besar dari angka apa pun
SELECT 'NaN'::float8 > 100 AS nan_lebih_besar,
       'NaN'::float8 = 'NaN'::float8 AS nan_sama_dengan_nan;

-- pembulatan NaN tetap menghasilkan NaN
SELECT round('NaN'::float8) AS bulatkan_nan;

pembulatan.sql

Unduh berkas
sql
-- round() pada numeric: nilai .5 dibulatkan menjauhi nol
SELECT round(2.5) AS positif_setengah,
       round(-2.5) AS negatif_setengah,
       round(2.4) AS di_bawah_setengah;

-- round() dengan jumlah angka desimal
SELECT round(123.456, 2) AS dua_desimal,
       round(123.456, 0) AS tanpa_desimal;

-- round() dengan tempat negatif: pembulatan puluhan dan ratusan
SELECT round(1234, -2) AS ratusan_bawah,
       round(1251, -2) AS ratusan_atas;

-- scale() melaporkan jumlah digit di belakang koma
SELECT scale(12.3450) AS skala_nilai;

round-float-error.sql

Unduh berkas
sql
-- round() dua argumen hanya tersedia untuk numeric, bukan double precision
SELECT round(3.14159::float8, 2) AS gagal;

sqrt-negatif.sql

Unduh berkas
sql
-- akar kuadrat dari bilangan negatif tidak ada di bilangan real
SELECT sqrt(-1) AS gagal;