Bilangan desimal (pecahan) memerlukan perhatian khusus karena representasinya di komputer tidak selalu eksak. Kotlin menyediakan dua tipe floating-point: Float (32 bit) dan Double (64 bit), keduanya mengikuti standar IEEE 754. Pada chapter ini kita akan membedah bagaimana kedua tipe ini menyimpan nilai, mengapa 0.1 + 0.2 tidak sama dengan 0.3, serta nilai khusus seperti NaN dan Infinity.

Dua tipe floating-point #

TipeLebarPresisiNilai terkecil positifNilai terbesar
Float32 bit6-7 digit desimal~1.4E-45~3.4E38
Double64 bit15-16 digit desimal~4.9E-324~1.8E308

Double adalah tipe default untuk literal desimal. Saat kita menulis 3.14, kompilator menyimpulkan Double. Untuk Float, gunakan suffix f (misalnya 3.14f).

Representasi IEEE 754 #

Standar IEEE 754 menyimpan bilangan dalam tiga bagian: sign (tanda), exponent (pangkat), dan mantissa (signifikansi). Karena mantissa memiliki panjang terbatas, banyak bilangan desimal tidak dapat direpresentasikan secara eksak dalam biner. Inilah sumber utama kesalahan pembulatan floating-point.

Contoh paling terkenal:

kotlin 0.1 + 0.2 != 0.3
val a = 0.1
val b = 0.2
val hasil = a + b
println(hasil)              // 0.30000000000000004
println(hasil == 0.3)       // false
println(hasil - 0.3)       // 5.551115123125783E-17

Selisih kecil ini (5.55E-17) tampak sepele, tetapi pada perhitungan berulang dapat terakumulasi menjadi kesalahan besar.

Demonstrasi floating-point #

Berikut program lengkap yang memperlihatkan tipe, presisi, dan nilai khusus floating-point:

kotlin
fun main() {
    val pi: Double = 3.141592653589793
    val suhuTubuh: Float = 36.6f
    val gravitasibumi = 9.81
    val dataKecil = 0.000001

    println("pi             = $pi")
    println("suhuTubuh      = $suhuTubuh")
    println("gravitasibumi  = $gravitasibumi")
    println("dataKecil      = $dataKecil")

    println()
    println("Ukuran bit:")
    println("Float  : ${Float.SIZE_BITS} bit | range ${Float.MIN_VALUE} .. ${Float.MAX_VALUE}")
    println("Double : ${Double.SIZE_BITS} bit | range ${Double.MIN_VALUE} .. ${Double.MAX_VALUE}")

    println()
    println("Demonstrasi presisi floating-point (IEEE 754):")
    val a = 0.1
    val b = 0.2
    val hasil = a + b
    println("0.1 + 0.2          = $hasil")
    println("Apakah == 0.3?     = ${hasil == 0.3}")
    println("Selisih dengan 0.3 = ${hasil - 0.3}")

    println()
    val harga = 199.99
    val qty = 3
    val total = harga * qty
    println("harga = $harga, qty = $qty")
    println("harga * qty = $total")

    println()
    println("Perbandingan Float vs Double untuk 1.0/3.0:")
    val floatResult = 1.0f / 3.0f
    val doubleResult = 1.0 / 3.0
    println("Float  1/3 = $floatResult")
    println("Double 1/3 = $doubleResult")

    println()
    val positifTakHingga = Double.POSITIVE_INFINITY
    val negatifTakHingga = Double.NEGATIVE_INFINITY
    val nan = Double.NaN
    println("POSITIVE_INFINITY = $positifTakHingga")
    println("NEGATIVE_INFINITY = $negatifTakHingga")
    println("NaN              = $nan")
    println("NaN == NaN?      = ${nan == nan}")
}

Output yang Dihasilkan:

text
pi             = 3.141592653589793
suhuTubuh      = 36.6
gravitasibumi  = 9.81
dataKecil      = 1.0E-6

Ukuran bit:
Float  : 32 bit | range 1.4E-45 .. 3.4028235E38
Double : 64 bit | range 4.9E-324 .. 1.7976931348623157E308

Demonstrasi presisi floating-point (IEEE 754):
0.1 + 0.2          = 0.30000000000000004
Apakah == 0.3?     = false
Selisih dengan 0.3 = 5.551115123125783E-17

harga = 199.99, qty = 3
harga * qty = 599.97

Perbandingan Float vs Double untuk 1.0/3.0:
Float  1/3 = 0.33333334
Double 1/3 = 0.3333333333333333

POSITIVE_INFINITY = Infinity
NEGATIVE_INFINITY = -Infinity
NaN              = NaN
NaN == NaN?      = false

Mari kita amati baris-baris penting pada kode di atas:

  1. Baris 2-5 — pi disimpan sebagai Double untuk presisi tinggi; suhuTubuh sebagai Float karena suffix f. Presisi Float hanya 6-7 digit, sehingga 3.141592653589793 terpotong jika disimpan sebagai Float.
  2. Baris 18-19 (Float.SIZE_BITS, Double.SIZE_BITS) — kedua tipe memiliki konstanta SIZE_BITS, MIN_VALUE, dan MAX_VALUE. MIN_VALUE di sini adalah nilai positif terkecil (bukan negatif terbesar).
  3. Baris 24-26 (0.1 + 0.2) — 0.1 dan 0.2 tidak dapat direpresentasikan eksak di biner; penjumlahannya menghasilkan 0.30000000000000004. Perbandingan == 0.3 mengembalikan false.
  4. Baris 32-33 (harga * qty) — 199.99 * 3 seharusnya 599.97. Karena 199.99 juga tidak eksak, hasilnya bisa saja sedikit berbeda dari harapan. Untuk perhitungan uang, gunakan BigDecimal (lihat catatan di bawah).
  5. Baris 39-40 (1.0f / 3.0f vs 1.0 / 3.0) — perbedaan presisi terlihat: Float memotong setelah 7-8 digit; Double mempertahankan hingga 16 digit.
  6. Baris 45 (nan == nan) — NaN tidak sama dengan dirinya sendiri. Ini adalah aturan IEEE 754. Gunakan nan.isNaN() untuk memeriksa NaN, bukan ==.

Nilai khusus: NaN, Infinity #

IEEE 754 mendefinisikan beberapa nilai khusus:

NilaiKonstanta KotlinCara memeriksa
Not-a-NumberDouble.NaN, Float.NaNx.isNaN()
Positif tak hinggaDouble.POSITIVE_INFINITYx.isInfinite()
Negatif tak hinggaDouble.NEGATIVE_INFINITYx.isInfinite()
Nol negatif-0.0x == 0.0 && 1/x < 0
kotlin Operasi menghasilkan NaN/Infinity
val a = 1.0 / 0.0          // Infinity
val b = -1.0 / 0.0         // -Infinity
val c = 0.0 / 0.0          // NaN
val d = Double.NaN + 1.0   // NaN (NaN menular)

Memilih Float atau Double #

KebutuhanTipe yang disarankan
Default untuk semua desimalDouble
Bacaan sensor, grafis 3D, data besarFloat (hemat memori)
Perhitungan uangBigDecimal (bukan floating-point)
Kalkulasi saintifik presisi tinggiDouble atau BigDecimal

Referensi Lanjutan #

  • Numbers — dokumentasi tipe angka Kotlin yang mencakup Float dan Double serta presisi IEEE 754

Pemahaman tentang jebakan floating-point membantu kita menulis kode numerik yang dapat dipercaya. Berikutnya, kita akan melihat tipe paling dasar dalam logika program — Boolean — pada chapter Boolean.