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 #
| Tipe | Lebar | Presisi | Nilai terkecil positif | Nilai terbesar |
|---|---|---|---|---|
Float | 32 bit | 6-7 digit desimal | ~1.4E-45 | ~3.4E38 |
Double | 64 bit | 15-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:
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-17Selisih 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:
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:
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? = falseMari kita amati baris-baris penting pada kode di atas:
- Baris 2-5 —
pidisimpan sebagaiDoubleuntuk presisi tinggi;suhuTubuhsebagaiFloatkarena suffixf. PresisiFloathanya 6-7 digit, sehingga3.141592653589793terpotong jika disimpan sebagaiFloat. - Baris 18-19 (
Float.SIZE_BITS,Double.SIZE_BITS) — kedua tipe memiliki konstantaSIZE_BITS,MIN_VALUE, danMAX_VALUE.MIN_VALUEdi sini adalah nilai positif terkecil (bukan negatif terbesar). - Baris 24-26 (
0.1 + 0.2) —0.1dan0.2tidak dapat direpresentasikan eksak di biner; penjumlahannya menghasilkan0.30000000000000004. Perbandingan== 0.3mengembalikanfalse. - Baris 32-33 (
harga * qty) —199.99 * 3seharusnya599.97. Karena199.99juga tidak eksak, hasilnya bisa saja sedikit berbeda dari harapan. Untuk perhitungan uang, gunakanBigDecimal(lihat catatan di bawah). - Baris 39-40 (
1.0f / 3.0fvs1.0 / 3.0) — perbedaan presisi terlihat:Floatmemotong setelah 7-8 digit;Doublemempertahankan hingga 16 digit. - Baris 45 (
nan == nan) —NaNtidak sama dengan dirinya sendiri. Ini adalah aturan IEEE 754. Gunakannan.isNaN()untuk memeriksa NaN, bukan==.
Nilai khusus: NaN, Infinity #
IEEE 754 mendefinisikan beberapa nilai khusus:
| Nilai | Konstanta Kotlin | Cara memeriksa |
|---|---|---|
| Not-a-Number | Double.NaN, Float.NaN | x.isNaN() |
| Positif tak hingga | Double.POSITIVE_INFINITY | x.isInfinite() |
| Negatif tak hingga | Double.NEGATIVE_INFINITY | x.isInfinite() |
| Nol negatif | -0.0 | x == 0.0 && 1/x < 0 |
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 #
| Kebutuhan | Tipe yang disarankan |
|---|---|
| Default untuk semua desimal | Double |
| Bacaan sensor, grafis 3D, data besar | Float (hemat memori) |
| Perhitungan uang | BigDecimal (bukan floating-point) |
| Kalkulasi saintifik presisi tinggi | Double atau BigDecimal |
Referensi Lanjutan #
- Numbers
— dokumentasi tipe angka Kotlin yang mencakup
FloatdanDoubleserta 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.