Java’da double
ve float
bilimsel ve mühendislik hesaplamaları için tasarlanmış türlerdir. Bu türler, ikili kayan noktalı işlemler (binary floating-point arithmetic) yapmak için tasarlarlanmıştır ancak net sonuçlar almak istediğinizde doğru çalışmazlar. Özellikle de parasal hesaplamalar yapmak için hiç uygun değildirler çünkü 0.1 değerini (ya da 10’un diğer negatif kuvvetlerini) double
veya float
kullanarak ifade etmek imkansızdır.
Örneğin, cebinizde 1.03 dolar olduğunu varsayalım ve 42 cent harcadınız. Ne kadar paranız kalmış olması lazım? Bunu öğrenmek için aşağıdaki kod satırını yazdığımızı düşünelim:
System.out.println(1.03 - 0.42);
Maalesef bu satır 0.6100000000000001
yazdıracaktır ve bu istisnai bir durum değildir. Cebinizde 1 dolar olsa ve her biri 10 cent olan 9 tane çivi almış olsanız, ne kadar para üstü almanız gerekir?
System.out.println(1.00 - 9 * 0.10);
Yukarıdaki kod satırına göre cevap 0.09999999999999998
olacaktır.
Bunu çözmek için sonucu yuvarlamak isteyebilirsiniz ama bu da her zaman çalışmaz. Diyelim ki cebinizde yine bir dolar var ve karşınızdaki rafta 10 cent, 20 cent, 30 cent şeklinde sıralanmış tanesi 1 dolara kadar giden şekerler var. Siz de 10 cent olandan başlayıp her birinden birer tane olacak şekilde olabildiğince çok sayıda şeker almak istiyorsunuz. Kaç tane şeker alabilirsiniz ve ne kadar para üstü kalır? Aşağıdaki programla bunu cevaplamaya çalışalım:
// Bozuk - parasal hesaplama için double kullanılıyor
public static void main(String[] args) {
double funds = 1.00;
int itemsBought = 0;
for (double price = 0.10; funds >= price; price += 0.10) {
funds -= price;
itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Change: $" + funds);
}
Bu programı çalıştırırsanız, sadece üç tane şeker alabildiğinizi ve para üstü olarak $0.3999999999999999
alacağınızı gösterir. Bu cevap yanlıştır! Bu kodu doğru yazmak için, parasal hesaplamalarda BigDecimal
, int
veya long
kullanmalıyız.
Aşağıda aynı kodu double
yerine BigDecimal
kullanarak yazdık. Dikkat ederseniz BigDecimal
yaratırken double
değil String
alan yapıcı metodu kullanmamız gerekmektedir.
public static void main(String[] args) {
final BigDecimal TEN_CENTS = new BigDecimal(".10");
int itemsBought = 0;
BigDecimal funds = new BigDecimal("1.00");
for (BigDecimal price = TEN_CENTS; funds.compareTo(price) >= 0; price = price.add(TEN_CENTS)) {
funds = funds.subtract(price);
itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Money left over: $" + funds);
}
Bu programı çalıştırdığınız zaman size dört tane şeker alabildiğinizi ve para üstü olarak $0.00
kaldığını gösterecektir, doğru cevap da budur.
BigDecimal
kullanmanın iki tane de dezavantajı vardır. Bunlardan birincisi temel türlere kıyasla kullanması pratik değildir ve ikincisi de yine temek türlere kıyasla çok yavaştır. Yavaşlık problemi kısa problemler için sorun yaratmayabilir ama kullanımının pratik olmaması biraz canınızı sıkabilir.
BigDecimal
kullanmak yerine int
veya long
gibi temel türler kullanabilir ve duruma göre ondalık değerleri kendiniz takip edebilirsiniz. Bu örnekte, bütün parasal değerleri dolar yerine cent cinsinden tutarsak aşağıdaki gibi bir kod yazabiliriz:
public static void main(String[] args) {
int itemsBought = 0;
int funds = 100;
for (int price = 10; funds >= price; price += 10) {
funds -= price;
itemsBought++;
}
System.out.println(itemsBought + " items bought.");
System.out.println("Cash left over: " + funds + " cents");
}
Özetle, bir matematiksel hesaplama yaparken tam cevaplar almak istiyorsanız float
veya double
kullanmaktan kaçınmalısınız. Eğer ondalık değerlerle uğraşmak istemiyorsanız ve performans kaybı önemli değilse BigDecimal
kullanabilirsiniz. BigDecimal
kullanmanın bir avantajı da size sekiz farklı yuvarlama modu sunmasıdır. Eğer yasal sebeplerden ötürü belli bir yuvarlama davranışı kullanmanız gerekiyorsa bunun faydasını görebilirsiniz. Eğer performans çok önemliyse ve ondalık değerleri kendiniz takip edebiliyorsanız, değerlerin büyüklüğüne göre int
veya long
kullanın.