Her zaman asla parayla double
veya float
türlerle para temsil etmem söylendi ve bu sefer size soruyorum: neden?
Eminim çok iyi bir sebep vardır, ne olduğunu bilmiyorum.
Her zaman asla parayla double
veya float
türlerle para temsil etmem söylendi ve bu sefer size soruyorum: neden?
Eminim çok iyi bir sebep vardır, ne olduğunu bilmiyorum.
Yanıtlar:
Çünkü şamandıralar ve çiftler, para için kullandığımız temel 10 katını doğru bir şekilde temsil edemez. Bu sorun yalnızca Java için değil, temel 2 kayan nokta türlerini kullanan herhangi bir programlama dili için de geçerlidir.
Temel 10'da, 10.25'i 1025 * 10 -2 ( 10'luk bir tam sayı çarpı) olarak yazabilirsiniz . IEEE-754 kayan nokta sayıları farklıdır, ancak bunları düşünmenin çok basit bir yolu, bunun yerine iki güçle çarpmaktır. Örneğin , aynı zamanda 10.25'e eşit olan 164 * 2 -4'e (tamsayı çarpı iki güç) bakabilirsiniz. Sayılar bellekte bu şekilde temsil edilmez, ancak matematik sonuçları aynıdır.
Taban 10'da bile, bu gösterim çoğu basit kesri doğru bir şekilde temsil edemez. Örneğin, 1/3'ü temsil edemezsiniz: ondalık gösterim tekrarlanır (0.3333 ...), bu nedenle 1/3 elde etmek için 10'luk bir güçle çarpabileceğiniz sonlu bir tamsayı yoktur. Sen 333333333 * 10 gibi 3 yılların uzun dizisinin ve küçük bir üs üzerinde yerleşmek olabilir -10 , ancak doğru değildir: 3 ile bu sen çarpın, sen 1 almazsınız eğer.
Bununla birlikte, para sayma amacıyla, en azından parası ABD doları büyüklüğünde bir değere sahip olan ülkeler için, genellikle tek ihtiyacınız olan 10 -2 katlarını saklayabilmektir , bu yüzden gerçekten önemli değil 1/3 temsil edilemez.
Şamandıralar ve çiftler ile ilgili sorun , para benzeri sayıların büyük çoğunluğunun 2 tamsayı çarpı olarak tam bir temsili olmamasıdır. tam IEEE-754 ikili kayan noktalı sayı olarak temsil edilebilecekleri için 0, 0,25, 0,5, 0,75 ve 1'dir. Diğerleri küçük bir miktar kapalıdır. 0.333333 örneğine benzer bir şekilde, kayan nokta değerini 0.1 olarak alırsanız ve 10 ile çarparsanız, 1 elde edemezsiniz.
Parayı bir olarak temsil etmek double
veya float
yazılım küçük hataları yuvarlarken ilk başta muhtemelen iyi görünecektir, ancak hatalı sayılar üzerinde daha fazla toplama, çıkarma, çarpma ve bölme gerçekleştirdiğinizde, hatalar birleşecek ve görünür olan değerlerle sonuçlanacaksınız kesin değil. Bu, yüzer ve çiftleri, temel 10 güçlerin katları için mükemmel doğruluk gerektiren para ile başa çıkmak için yetersiz kılar.
Hemen hemen her dilde çalışan bir çözüm, bunun yerine tamsayıları kullanmak ve sentleri saymaktır. Örneğin, 1025 $ 10.25 olurdu. Birçok dilde parayla başa çıkmak için yerleşik türler vardır. Diğerlerinin yanı sıra, Java BigDecimal
sınıfı ve C # decimal
türü vardır.
1.0 / 10 * 10
1.0 ile aynı olmayabilir.
Bloch, J., Etkili Java, 2. baskı, Madde 48:
float
Vedouble
bunun 0.1 (ya da on başka negatif güç) bir şekilde temsil etmek için mümkün değildir, çünkü tip özellikle para hesaplamalar için kötü uygundurfloat
ya dadouble
tam olarak.Örneğin, 1.03 $ 'ınız olduğunu ve 42c harcadığınızı varsayalım. Ne kadar paranız kaldı?
System.out.println(1.03 - .42);
yazdırır
0.6100000000000001
.Bu sorunu çözmek için doğru yol kullanmaktır
BigDecimal
,int
yalong
parasal hesaplamalar için.
BigDecimal
Bazı uyarılar olsa da (lütfen şu anda kabul edilen cevaba bakınız).
long a = 104
dolar yerine sent kullanır ve sayarsınız .
BigDecimal
.
Ne doğruluk meselesi ne de kesinlik meselesi. Temel 2 yerine hesaplamalar için taban 10'u kullanan insanların beklentilerini karşılama meselesidir. Örneğin, finansal hesaplamalar için iki katını kullanmak, matematiksel anlamda "yanlış" cevaplar üretmez, ancak finansal anlamda beklenen şey değil.
Sonuçlarınızı çıktıdan önceki son dakikada tamamlasanız bile, beklentileri karşılamayan çiftleri kullanarak ara sıra sonuç alabilirsiniz.
Bir hesap makinesi kullanarak veya sonuçları elle hesaplayarak, tam olarak 1.40 * 165 = 231. Ancak, dahili olarak çiftler kullanarak, derleyici / işletim sistemi ortamımda, 230.99999'a yakın bir ikili sayı olarak saklanır ... böylece sayıyı kısaltırsanız, 231 yerine 230 alırsınız. Kesmek yerine yuvarlamanın 231 istenen sonucu vermiştir. Bu doğrudur, ancak yuvarlama her zaman kesmeyi içerir. Hangi yuvarlama tekniğini kullanırsanız kullanın, yuvarlanmasını beklediğinizde yuvarlanacak olan bunun gibi hala sınır koşulları vardır. Sıradan testler veya gözlem yoluyla sıklıkla bulunamayacak kadar nadirdirler. Beklendiği gibi davranmayan sonuçları gösteren örnekleri aramak için bazı kodlar yazmanız gerekebilir.
Bir şeyi en yakın kuruşa yuvarlamak istediğinizi varsayın. Böylece nihai sonucunuzu alın, 100 ile çarpın, 0,5 ekleyin, kısaltın ve ardından kuruşlara geri dönmek için sonucu 100'e bölün. Kaydettiğiniz dahili numara 3.465 yerine 3.46499999 .... ise, numarayı en yakın kuruşa yuvarladığınızda 3.47 yerine 3.46 elde edersiniz. Ancak, temel 10 hesaplamalarınız cevabın tam olarak 3.465 olması gerektiğini belirtmiş olabilir, bu da 3.46'ya değil, 3.47'ye yuvarlanmalıdır. Bu tür şeyler, finansal hesaplamalar için iki katını kullandığınızda gerçek hayatta bazen meydana gelir. Nadirdir, bu nedenle genellikle bir sorun olarak fark edilmez, ancak olur.
Dahili hesaplarınız için iki kat yerine taban 10 kullanırsanız, yanıtlar her zaman tam olarak insanlar tarafından beklenen, kodunuzda başka bir hata olmadığı varsayılır.
Math.round(0.49999999999999994)
1 döndürür?
Bu yanıtların bazıları beni rahatsız ediyor. Finansal hesaplamalarda çiftlerin ve yüzenlerin bir yeri olduğunu düşünüyorum. Kesinlikle, kesirli olmayan parasal tutarları eklerken ve çıkarırken, tamsayı sınıfları veya BigDecimal sınıfları kullanılırken hassasiyet kaybı olmaz. Ancak daha karmaşık işlemler gerçekleştirirken, sayıları nasıl saklarsanız saklayın, genellikle birkaç veya çok sayıda ondalık basamaktan çıkan sonuçlar elde edersiniz. Sorun sonucu nasıl sunacağınızdır.
Sonucunuz yuvarlama ve yuvarlama arasındaki sınırda ise ve son kuruş gerçekten önemliyse, muhtemelen izleyiciye cevabın neredeyse ortada olduğunu söylemelisiniz - daha fazla ondalık basamak göstererek.
Çiftler ve daha çok şamandıralar ile ilgili problem, büyük sayıları ve küçük sayıları birleştirmek için kullanıldıklarıdır. Java’da,
System.out.println(1000000.0f + 1.2f - 1000000.0f);
sonuç
1.1875
Şamandıralar ve çiftler yaklaşık değerlerdir. Bir BigDecimal oluşturursanız ve bir şamandırayı kurucuya geçirirseniz şamandıranın gerçekte neye eşit olduğunu görürsünüz:
groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375
bu muhtemelen 1,01 doları temsil etmek istemezsiniz.
Sorun, IEEE spesifikasyonunun tüm kesirleri tam olarak temsil etmenin bir yolu olmamasıdır, bazıları da kesirleri tekrarlamakla sonuçlanır, böylece yaklaşık hatalarla sonuçlanırsınız. Muhasebeciler şeyleri tam olarak kuruşa çıkmayı sevdiğinden ve müşteriler faturalarını öderlerse sinirlenirler ve ödeme işlendikten sonra 0,01 borçludurlar ve ücret alırlar veya hesaplarını kapatamazlar, kullanmak daha iyidir ondalık (C # olarak) veya Java'daki java.math.BigDecimal gibi tam türler.
Yuvarlarsanız hata kontrol edilemez: Peter Lawrey'in bu makalesine bakın . İlk etapta yuvarlamak zorunda değilsiniz. Parayı işleyen çoğu uygulama çok fazla matematik gerektirmez, işlemler bir şeyler eklemekten veya farklı kovalara miktar tahsis etmekten oluşur. Kayan noktaya ve yuvarlamaya başlamak işleri karmaşıklaştırır.
float
, double
ve kesin değerleri BigDecimal
temsil eder . Koddan nesneye dönüştürme, diğer işlemlerin yanı sıra tam değildir. Türlerin kendileri tam değildir.
Düşürülme riskiyle karşı karşıya kalacağım, ancak kayan nokta sayılarının para birimi hesaplamaları için uygunluğunun abartıldığını düşünüyorum. Cent yuvarlamayı doğru yaptığınızdan ve zneak tarafından açıklanan ikili ondalık gösterim uyumsuzluğuna karşı koymak için çalışmak için yeterince önemli basamaklara sahip olduğunuzdan emin olun, sorun olmayacaktır.
Excel'de para birimi ile hesaplanan insanlar her zaman çift duyarlıklı şamandıralar kullanmıştır (Excel'de para birimi türü yoktur) ve henüz yuvarlama hataları hakkında şikayet eden birini görmedim.
Tabii ki, mantık içinde kalmak zorundasınız; Örneğin, basit bir web mağazası muhtemelen çift duyarlıklı kayan reklamlarla ilgili herhangi bir sorun yaşamaz, ancak örneğin muhasebe veya çok sayıda (sınırsız) sayı eklemeyi gerektiren başka bir şey yaparsanız, kayan nokta numaralarına on ayağı dokunmak istemezsiniz. kutup.
Kayan nokta türünün yalnızca yaklaşık ondalık verileri temsil edebileceği doğru olsa da, bir kişi sayıları sunmadan önce gerekli hassasiyete yuvarlarsa, doğru sonucu elde eder. Genelde.
Genellikle çift tip 16 rakamdan daha az bir hassasiyete sahip olduğundan. Daha iyi bir hassasiyete ihtiyacınız varsa, bu uygun bir tip değildir. Ayrıca yaklaşımlar birikebilir.
Sabit nokta aritmetiği kullansanız bile, sayıları yuvarlamak zorunda olmanız gerektiği söylenmelidir, periyodik ondalık sayılar elde ederseniz BigInteger ve BigDecimal'ın hata vermesi değil. Yani burada da bir yaklaşım var.
Örneğin, geçmişte finansal hesaplamalar için kullanılan COBOL, maksimum 18 rakam hassasiyetine sahiptir. Dolayısıyla genellikle örtük bir yuvarlama vardır.
Sonuç olarak, bence çift yaklaşık 16 basamaklı hassasiyeti için uygun değildir, ki bu yaklaşık değil, yetersiz olabilir.
Sonraki programın aşağıdaki çıktısını göz önünde bulundurun. Bu, çift yuvarlamadan sonra hassasiyet 16'ya kadar BigDecimal ile aynı sonucu verdiğini gösterir.
Precision 14
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.000051110111115611
Double : 56789.012345 / 1111111111 = 0.000051110111115611
Precision 15
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.0000511101111156110
Double : 56789.012345 / 1111111111 = 0.0000511101111156110
Precision 16
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.00005111011111561101
Double : 56789.012345 / 1111111111 = 0.00005111011111561101
Precision 17
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.000051110111115611011
Double : 56789.012345 / 1111111111 = 0.000051110111115611013
Precision 18
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.0000511101111156110111
Double : 56789.012345 / 1111111111 = 0.0000511101111156110125
Precision 19
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.00005111011111561101111
Double : 56789.012345 / 1111111111 = 0.00005111011111561101252
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;
public class Exercise {
public static void main(String[] args) throws IllegalArgumentException,
SecurityException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
String amount = "56789.012345";
String quantity = "1111111111";
int [] precisions = new int [] {14, 15, 16, 17, 18, 19};
for (int i = 0; i < precisions.length; i++) {
int precision = precisions[i];
System.out.println(String.format("Precision %d", precision));
System.out.println("------------------------------------------------------");
execute("BigDecimalNoRound", amount, quantity, precision);
execute("DoubleNoRound", amount, quantity, precision);
execute("BigDecimal", amount, quantity, precision);
execute("Double", amount, quantity, precision);
System.out.println();
}
}
private static void execute(String test, String amount, String quantity,
int precision) throws IllegalArgumentException, SecurityException,
IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
Method impl = Exercise.class.getMethod("divideUsing" + test, String.class,
String.class, int.class);
String price;
try {
price = (String) impl.invoke(null, amount, quantity, precision);
} catch (InvocationTargetException e) {
price = e.getTargetException().getMessage();
}
System.out.println(String.format("%-30s: %s / %s = %s", test, amount,
quantity, price));
}
public static String divideUsingDoubleNoRound(String amount,
String quantity, int precision) {
// acceptance
double amount0 = Double.parseDouble(amount);
double quantity0 = Double.parseDouble(quantity);
//calculation
double price0 = amount0 / quantity0;
// presentation
String price = Double.toString(price0);
return price;
}
public static String divideUsingDouble(String amount, String quantity,
int precision) {
// acceptance
double amount0 = Double.parseDouble(amount);
double quantity0 = Double.parseDouble(quantity);
//calculation
double price0 = amount0 / quantity0;
// presentation
MathContext precision0 = new MathContext(precision);
String price = new BigDecimal(price0, precision0)
.toString();
return price;
}
public static String divideUsingBigDecimal(String amount, String quantity,
int precision) {
// acceptance
BigDecimal amount0 = new BigDecimal(amount);
BigDecimal quantity0 = new BigDecimal(quantity);
MathContext precision0 = new MathContext(precision);
//calculation
BigDecimal price0 = amount0.divide(quantity0, precision0);
// presentation
String price = price0.toString();
return price;
}
public static String divideUsingBigDecimalNoRound(String amount, String quantity,
int precision) {
// acceptance
BigDecimal amount0 = new BigDecimal(amount);
BigDecimal quantity0 = new BigDecimal(quantity);
//calculation
BigDecimal price0 = amount0.divide(quantity0);
// presentation
String price = price0.toString();
return price;
}
}
Kayan nokta sayısının sonucu kesin değildir, bu da onları kesin sonuç gerektiren ve yaklaşık olarak tahmin edilmeyen herhangi bir finansal hesaplama için uygun hale getirmez. float ve double mühendislik ve bilimsel hesaplama için tasarlanmıştır ve çoğu zaman kesin sonuç vermez ayrıca kayan nokta hesaplamasının sonucu JVM'den JVM'ye kadar değişebilir. Aşağıda, para değerini temsil etmek için kullanılan BigDecimal ve double ilkel örneğine bakın, kayan nokta hesaplamasının kesin olmayabileceği ve finansal hesaplamalar için BigDecimal kullanması gerektiği açıktır.
// floating point calculation
final double amount1 = 2.0;
final double amount2 = 1.1;
System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));
// Use BigDecimal for financial calculation
final BigDecimal amount3 = new BigDecimal("2.0");
final BigDecimal amount4 = new BigDecimal("1.1");
System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));
Çıktı:
difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9
double
, ondalık FP değerinde olduğu gibi 0,5 sent'i hesaplamakta sorun yaşamayacaktı. Kayan nokta hesaplamaları ikili FP veya ondalık FP aracılığıyla örneğin 123.499941 ¢ faiz değeri verirse, çift yuvarlama sorunu aynıdır - her iki durumda da avantaj yoktur. Öncülünüz matematiksel olarak kesin değeri varsayar ve ondalık FP aynıdır - ondalık FP bile bir şey garanti etmez. 0.5 / 7.0 * 7.0 ikili ve deikmal FP için bir problemdir. IAC, C'nin bir sonraki versiyonunun ondalık FP sağlamasını beklediğimden çoğu tartışmalı olacak.
Daha önce de belirtildiği gibi "Parayı bir çift veya kayan nokta olarak temsil etmek, yazılım küçük hataları yuvarlarken ilk başta muhtemelen iyi görünecektir, ancak hatalı sayılar üzerinde daha fazla toplama, çıkarma, çarpma ve bölme gerçekleştirdikçe, daha fazla hassasiyet kaybedeceksiniz Bu, yüzer ve çiftleri, temel 10 güçlerin katları için mükemmel doğruluk gerektiren para ile başa çıkmak için yetersiz kılar. "
Sonunda Java'nın Para ve Para ile çalışmak için standart bir yolu var!
JSR 354: Para ve Para Birimi API'sı
JSR 354, Para ve Para Birimi ile kapsamlı hesaplamaları temsil etmek, taşımak ve gerçekleştirmek için bir API sağlar. Bu bağlantıdan indirebilirsiniz:
JSR 354: Para ve Para Birimi API'sı İndir
Şartname aşağıdaki şeylerden oluşur:
- Parasal tutarlar ve para birimlerini işlemek için bir API
- Değiştirilebilir uygulamaları destekleyen API'ler
- Uygulama sınıflarının örneklerini oluşturan fabrikalar
- Parasal tutarların hesaplanması, dönüştürülmesi ve biçimlendirilmesi için işlevsellik
- Java 9'a eklenmesi planlanan Para ve Para Birimleri ile çalışmak için Java API.
- Tüm spesifikasyon sınıfları ve arayüzler javax.money. * Paketinde bulunur.
JSR 354 Örneği: Para ve Para Birimi API'sı:
Bir MonetaryAmount oluşturma ve konsola yazdırma örneği şuna benzer:
MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
Referans uygulama API'sini kullanırken, gerekli kod çok daha basittir:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
API ayrıca MonetaryAmounts ile hesaplamaları da destekler:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));
Para Birimi ve Parasal Miktar
// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MonetaryAmount, atanan para birimine, sayısal tutara, kesinliğine ve daha fazlasına erişmeyi sağlayan çeşitli yöntemlere sahiptir:
MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();
int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5
// NumberValue extends java.lang.Number.
// So we assign numberValue to a variable of type Number
Number number = numberValue;
MonetaryAmount'lar bir yuvarlama operatörü kullanılarak yuvarlanabilir:
CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35
MonetaryAmounts koleksiyonlarıyla çalışırken, filtreleme, sıralama ve gruplama için bazı güzel yardımcı yöntemler mevcuttur.
List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));
Özel Parasal Montaj işlemleri
// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
return Money.of(tenPercent, amount.getCurrency());
};
MonetaryAmount dollars = Money.of(12.34567, "USD");
// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567
Kaynaklar:
JSR 354 ile Java'da para ve para birimlerini işleme
Java 9 Para ve Para Birimi API'sini inceleme (JSR 354)
Ayrıca Bakınız: JSR 354 - Para Birimi ve Para
Hesaplamanız çeşitli adımlar içeriyorsa, keyfi hassas aritmetik% 100'ü kapsamaz.
Sonuçların mükemmel bir temsilini kullanmanın tek güvenilir yolu (Bölme işlemlerini son adıma toplu yapacak özel bir Kesir veri türü kullanın) ve yalnızca son adımda ondalık gösterime dönüştürün.
Keyfi kesinlik yardımcı olmaz çünkü her zaman çok fazla ondalık basamağa sahip sayılar olabilir veya gibi bazı sonuçlar olabilir 0.6666666
. Böylece her adımda küçük hatalarınız olacaktır.
Bu hatalar eklenir, sonunda artık göz ardı edilmesi kolay olmayabilir. Buna Hata Yayılımı denir .
Çoğu cevap, kişinin para ve para birimi hesaplamaları için iki katını kullanmama nedenlerini vurgulamıştır. Onlara tamamen katılıyorum.
Bu, çiftlerin asla bu amaç için kullanılamayacağı anlamına gelmez.
Çok düşük gc gereksinimleri olan bir dizi proje üzerinde çalıştım ve BigDecimal nesnelerine sahip olmak bu yüke büyük bir katkıda bulundu.
Bu, akıllıca bir öneri getiren doğruluk ve kesinlik ile ilgili çifte temsil anlayışı ve deneyim eksikliğidir.
Hangi çift değer aralığının ele alındığına bağlı olarak yapılması gereken projenizin hassasiyet ve doğruluk gereksinimlerini karşılayabiliyorsanız, çalışmasını sağlayabilirsiniz.
Daha fazla fikir edinmek için guava'nın FuzzyCompare yöntemine başvurabilirsiniz. Parametre toleransı anahtardır. Bir menkul kıymet alım satım uygulaması için bu sorunu ele aldık ve farklı aralıklarda farklı sayısal değerler için hangi toleransların kullanılacağı konusunda kapsamlı bir araştırma yaptık.
Ayrıca, karma haritayı uygulama olarak çift anahtarları bir harita anahtarı olarak kullanmaya çalıştığınız durumlar olabilir. Çok risklidir çünkü Double.equals ve karma kodu örneğin "0,5" ve "0,6 - 0,1" değerleri büyük karışıklığa neden olur.
Bu soruya gönderilen cevapların çoğu IEEE ve kayan noktalı aritmetiği çevreleyen standartları tartışmaktadır.
Bilgisayar dışı bilim geçmişinden (fizik ve mühendislik) geliyor, problemlere farklı bir bakış açısıyla bakma eğilimindeyim. Benim için, matematiksel bir hesaplamada bir çift ya da şamandıra kullanmamamın nedeni, çok fazla bilgi kaybetmem.
Alternatifleri nelerdir? Çok var (ve daha pek çoğu farkında değilim!).
Java'daki BigDecimal, Java diline özgüdür. Apfloat, Java için bir başka keyfi hassas kütüphanedir.
C # 'daki ondalık veri türü, 28 önemli rakam için Microsoft'un .NET alternatifidir.
SciPy (Scientific Python) muhtemelen finansal hesaplamaları da yapabilir (denemedim, ama şüpheliyim).
GNU Çoklu Hassas Kütüphane (GMP) ve GNU MFPR Kütüphanesi, C ve C ++ için iki ücretsiz ve açık kaynak kaynaktır.
JavaScript (!) İçin sayısal hassas kütüphaneler de var ve bence finansal hesaplamaları yapabilen PHP.
Ayrıca birçok bilgisayar dili için özel (özellikle Fortran için) ve açık kaynaklı çözümler de vardır.
Eğitim alarak bilgisayar bilimcisi değilim. Ancak, Java BigDecimal veya C # ondalık eğilimi eğilimindedir. Listelediğim diğer çözümleri denemedim, ancak muhtemelen de çok iyi.
Benim için desteklediği yöntemler nedeniyle BigDecimal'ı seviyorum. C # 'ın ondalığı çok güzel, ama istediğim kadar onunla çalışma şansım olmadı. Boş zamanlarımda ilgimi çeken bilimsel hesaplamalar yapıyorum ve BigDecimal çok iyi çalışıyor gibi görünüyor çünkü kayan nokta sayılarımın hassasiyetini ayarlayabiliyorum. BigDecimal'ın dezavantajı? Özellikle bölme yöntemini kullanıyorsanız, bazen yavaş olabilir.
Hız için C, C ++ ve Fortran'daki ücretsiz ve tescilli kütüphanelere bakabilirsiniz.
Önceki yanıtları eklemek için Joda-Money uygulama seçeneği de vardır , soruda ele alınan sorunla uğraşırken, BigDecimal'in yanı sıra Java'da . Java modül adı org.joda.money'dir.
Java SE 8 veya üstünü gerektirir ve bağımlılığı yoktur.
Daha kesin olmak gerekirse, derleme zamanı bağımlılığı vardır, ancak zorunlu değildir.
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
Joda Money kullanımına örnekler:
// create a monetary value
Money money = Money.parse("USD 23.87");
// add another amount with safe double conversion
CurrencyUnit usd = CurrencyUnit.of("USD");
money = money.plus(Money.of(usd, 12.43d));
// subtracts an amount in dollars
money = money.minusMajor(2);
// multiplies by 3.5 with rounding
money = money.multipliedBy(3.5d, RoundingMode.DOWN);
// compare two amounts
boolean bigAmount = money.isGreaterThan(dailyWage);
// convert to GBP using a supplied rate
BigDecimal conversionRate = ...; // obtained from code outside Joda-Money
Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);
// use a BigMoney for more complex calculations where scale matters
BigMoney moneyCalc = money.toBigMoney();
Belgeler: http://joda-money.sourceforge.net/apidocs/org/joda/money/Money.html
Uygulama örnekleri: https://www.programcreek.com/java-api-examples/?api=org.joda.money.Money
Bazı örnek ... bu (aslında beklendiği gibi çalışmaz), hemen hemen her programlama dilinde ... Delphi, VBScript, Visual Basic, JavaScript ve şimdi Java / Android ile denedim:
double total = 0.0;
// do 10 adds of 10 cents
for (int i = 0; i < 10; i++) {
total += 0.1; // adds 10 cents
}
Log.d("round problems?", "current total: " + total);
// looks like total equals to 1.0, don't?
// now, do reverse
for (int i = 0; i < 10; i++) {
total -= 0.1; // removes 10 cents
}
// looks like total equals to 0.0, don't?
Log.d("round problems?", "current total: " + total);
if (total == 0.0) {
Log.d("round problems?", "is total equal to ZERO? YES, of course!!");
} else {
Log.d("round problems?", "is total equal to ZERO? NO... thats why you should not use Double for some math!!!");
}
ÇIKTI:
round problems?: current total: 0.9999999999999999
round problems?: current total: 2.7755575615628914E-17
round problems?: is total equal to ZERO? NO... thats why you should not use Double for some math!!!
Şamandıra, farklı tasarımlı Ondalık'ın ikili biçimidir; onlar iki farklı şeydir. Birbirine dönüştürüldüğünde iki tür arasında çok az hata vardır. Ayrıca, şamandıra bilimsel için sonsuz sayıda değeri temsil edecek şekilde tasarlanmıştır. Bu, sabit sayıda bayt ile aşırı küçük ve aşırı büyük sayıya karşı hassasiyeti kaybetmek için tasarlandığı anlamına gelir. Ondalık sonsuz sayıda değeri temsil edemez, sadece bu ondalık basamak sayısına bağlanır. Yani Float ve Ondalık farklı amaçlar içindir.
Para birimi değeri hatasını yönetmenin bazı yolları vardır:
Uzun tamsayı kullanın ve bunun yerine sent cinsinden sayın.
Çift hassasiyet kullanın, önemli basamaklarınızı yalnızca 15 olarak tutun, böylece ondalık sayı tam olarak simüle edilebilir. Değerleri sunmadan önce yuvarlayın; Hesaplamalar yaparken sık sık yuvarlayın.
Java BigDecimal gibi bir ondalık kütüphane kullanın, böylece ondalığı simüle etmek için çift kullanmanız gerekmez.
ps el bilimsel hesap makineleri markaların çoğu float yerine ondalık üzerinde çalıştığını bilmek ilginç. Yani hiç kimse şamandıra dönüşüm hataları şikayet.