BigDecimal — Parada Kuruş Kaybetmemek
Önce şunu oku: String — Hiç Değişmeyen Metin
30 saniyede özet
double ondalık sayıları yaklaşık tutar, bu yüzden para hesabında kuruş kaçırır. BigDecimal her haneyi tam tutar; onu metinden kurar, bölerken yuvarlama kuralını söyler ve karşılaştırırken compareTo kullanırsın.
Üç arkadaş 100 liralık bir kafe hesabını bölüşüyorsunuz ve biri “herkes 33 lira 33 kuruş” diyor. Geriye bir kuruş kaldı; kim ödeyecek?
Para hesabında bu küçük soru hep vardır. Java’da onu doğru cevaplamanın aracı tek bir sınıftır.
Adım adım oku
- İki kasa da 0.10 ile 0.20'yi topluyor.
- double ile çalışan kasa 0.30000000000000004 buluyor: tam değil.
- BigDecimal ile çalışan kasa tam olarak 0.30 buluyor.
- Parada kıl payı da bir hatadır; bu yüzden para BigDecimal ile tutulur.
-
Bayt: 0.1 ile 0.2'yi topladım, 0.30000000000000004 çıktı. Bilgisayar bozuk mu?
-
Sen: Belki toplamayı yanlış yazdın?
-
Bayt: Hayır, toplama doğru. Sorun sayının kendisinde: 0.1 tam olarak saklanamıyor.
-
Bayt: Para için tam saklayan bir tip var. Gel onunla tanışalım.
double neden kuruş kaçırır?
double sayıları ikilik sistemde, sınırlı sayıda haneyle tutar. Buna kayan noktaSayıları ikilik sistemde sınırlı sayıda haneyle, yaklaşık olarak tutan gösterim. Java'da float ve double böyledir.0.5 gibi bazı ondalık sayılar tam sığar, 0.1 gibi çoğu sığmaz ve en yakın değere yuvarlanır. Bu yüzden para hesabında BigDecimal kullanılır.Sözlükte gör → sayı denir. 0.5 gibi bazı sayılar tam sığar, ama 0.1 gibi çoğu ondalık sayı sığmaz ve en yakın değere yuvarlanır.
System.out.println(0.1 + 0.2); // 0.30000000000000004
double toplam = 0;for (int i = 0; i < 10; i++) { toplam += 0.1;}System.out.println(toplam); // 0.9999999999999999Tek bir hesapta fark çok küçüktür. Ama milyonlarca işlemde birikir, faizde çarpılır ve ekstrede görünür.
Kasa kuruşları sayıyor
Tohum 478141| tutar | double × 100 | kuruş | doğrusu |
|---|
Oynat ya da adımla: her adımda dört tutar çevrilir.
Şu an ne oldu?
(long) (tutar * 100) · Satış fiyatları (2 hane)
Kasa her tutarı kuruşa çevirecek, gün sonunda toplamı beklenenle karşılaştıracak.
Görevler0/3
Bir günde en az 8 kuruş kaybetaçık
İpucu
Varsayılan ayarlar yeter.
Math.round ile bile yanlış yuvarlanan bir tutar bulaçık
İpucu
Üç haneli tutarlara bak.
Faiz tutarlarını hatasız çevir ve mutabakatı tutturaçık
İpucu
Sayıyı metinden kur, kuralı söyle.
Olay günlüğü (0)
Henüz olay yok. Oynat veya adımla.
- Varsayılanla oynat.
(long) (tutar * 100)on iki fiyattan sekizinde bir kuruş kesti; gün sonu toplamı tutmadı. - “Math.round” seç. Fiyatlar düzeldi, ama double toplam yine 63.38’e eşit değil.
- “Faiz tutarları”na geç. Math.round bile 1.005’i 100’e yuvarladı; doğrusu 101.
- “BigDecimal + HALF_UP” seç. Her tutar doğru, mutabakat tuttu.
Kafam karıştı, daha basit anlat
double bir tartı gibidir: hızlıdır ama gramı gramına tartmaz. Para için hassas terazi gerekir; o da BigDecimal.
`0.1 + 0.2` işlemi Java'da neden `0.30000000000000004` verir?
BigDecimal: her hane tam
`System.out.println(new BigDecimal(0.1));` ne yazdırır? Cevabı göster
0.1000000000000000055511151231257827021181583404541015625. double’a çevrilmiş 0.1 zaten yaklaşıktı; BigDecimal o yaklaşık değeri tam olarak kopyaladı.
BigDecimal yazdığın haneleri aynen saklar, ama ona tam haneleri vermen gerekir. Bu yüzden onu metinden kurarsın; bir double’dan kurarsan double’ın hatasını da kopyalarsın.
new BigDecimal("0.10") // tam: 0.10BigDecimal.valueOf(0.1) // 0.1 (double'ın kısa metin hâli üzerinden)new BigDecimal(0.1) // 0.1000000000000000055… kaçınBigDecimal de String gibi immutableOluşturulduktan sonra içi değişmeyen nesne. Anahtar olarak güvenlidir, çünkü hashCode'u hiç değişmez.Sözlükte gör →’dır. add, subtract, multiply eski sayıyı değiştirmez, yeni bir sonuç döndürür: toplam = toplam.add(tutar);
Kafam karıştı, daha basit anlat
BigDecimal’e sayıyı yazıyla ver: “0.10”. Böylece ne yazdıysan o saklanır. İşlem sonucunu da yine bir değişkene koymayı unutma.
10 lira 10 kuruşu BigDecimal olarak tam doğru kurmanın yolu hangisi?
Program ne yazdırır?
Satır satır: bölmek ve yuvarlamak
Toplamak ve çarpmak kolaydır. Asıl dikkat isteyen bölmedir: 100’ü 3’e bölmenin sonu yoktur.
100 lirayı üç kişiye bölmek
BigDecimal hesap = new BigDecimal("100.00");BigDecimal kisi = new BigDecimal("3"); BigDecimal pay = hesap.divide(kisi);BigDecimal pay2 = hesap.divide(kisi, 2, RoundingMode.HALF_EVEN);BigDecimal kalan = hesap.subtract(pay2.multiply(kisi));Debug
hazırlık Tutarlar metinden kuruluyor; iki hane: 100.00.
- hesap
- = 100.00
- kisi
- = 3
Sol/sağ ok tuşlarıyla da gezebilirsin.
`new BigDecimal("100").divide(new BigDecimal("3"))` çalıştırılınca ne olur?
Aynı değer, farklı ölçek
BigDecimal bir sayının yanında kaç ondalık hanesi olduğunu da (ölçekBir BigDecimal sayısının ondalık kısmında kaç hane olduğu. 2.50 sayısının ölçeği 2'dir.equals ölçeği de karşılaştırır: 2.0 ile 2.00 değerce eşit olsa da equals onlara farklı der. setScale ile ölçek sabitlenir.Sözlükte gör →) saklar. 2.0 ile 2.00 aynı değerdir ama ölçekleri farklıdır.
BigDecimal a = new BigDecimal("2.0");BigDecimal b = new BigDecimal("2.00");
a.equals(b); // false: ölçekler farklıa.compareTo(b) == 0; // true: değerler aynıİki tutarın aynı olup olmadığını sorarken compareTo kullan. Tutarları sakladığın yerde ölçeği sabitlemek de (setScale(2, RoundingMode.HALF_EVEN)) bu sürprizi baştan önler.
Kafam karıştı, daha basit anlat
2 lira ile 2 lira 00 kuruş aynı paradır, sadece farklı yazılmıştır. equals yazılışa da bakar, compareTo yalnızca miktara.
Program ne yazdırır?
Bankada bunu en sık faiz hesabında görürsün: yıllık oran günlüğe bölünür, sonuç kuruşa yuvarlanır ve yuvarlama kuralı açıkça yazılır.
Derinleş · Mevduat faizi: tam hesap, açık yuvarlama 3 dosya · ~40 satır · ilk okumada atlayabilirsin
Tuzaklar
new BigDecimal(double). double’ın yaklaşık değerini kopyalar. Metinden kur ya da BigDecimal.valueOf kullan.
Yuvarlama kuralını düşünmeden seçmek. HALF_UP okulda öğrendiğin yuvarlamadır; HALF_EVEN tam ortadaki sayıları en yakın çift haneye yuvarlar ve çok sayıda işlemde yukarı kaymayı dengeler. Hangisinin kullanılacağını genellikle iş kuralı ya da mevzuat belirler.
Yolda double’a dönmek. Tutarı JSON’da, veritabanında ya da bir ara hesapta double yaparsan kazandığın hassasiyeti orada kaybedersin. Tutar baştan sona BigDecimal kalmalı.
Bir kampanyada her müşteriye 0.10 TL iade veriliyor. Gün sonu toplamı birkaç kuruş tutmuyor. Hatalı satır hangisi?
Kendini sına
0.1 + 0.2 işlemi double ile tam olarak 0.3 verir.
`RoundingMode.HALF_EVEN` ile 2.345 iki haneye yuvarlanırsa sonuç ne olur?
Aklında kalacak üç şey
- 1 double ondalık sayıları yaklaşık tutar; para hesabı her zaman BigDecimal ile yapılır ve BigDecimal metinden kurulur.
- 2 BigDecimal ile bölerken kaç hane ve hangi yuvarlama kuralı istediğini söylersin; söylemezsen bitmeyen bölümde hata alırsın.
- 3 2.0 ile 2.00'ın equals'ı false döner; iki tutarın aynı değerde olup olmadığını compareTo söyler.
4 kart sonraki derste seni bekliyor