Внимание: Двойно към BigDecimal в Java

Комбинацията от голямата световна база за разработчици на Java и лесно достъпната онлайн документация за API доведе до цялостна и точна документация за Java SE API. Все още има ъгли, които може да не са толкова задълбочени или точни, колкото би се искало, но документацията за API обикновено е доста добра както по отношение на задълбочеността и точността.

Въпреки че документацията за API на Javadoc стана доста полезна, ние, разработчиците, често бързаме и често се чувстваме толкова уверени в собствените си способности, че е почти неизбежно, че понякога ще продължим да се опитваме да правим нещата, без първо да прочетем ръководството. Поради тази тенденция, понякога можем да се изгорим, като злоупотребяваме с определен API, въпреки документацията, която ни предупреждава да не го (използваме) по този начин. Обсъдих това в публикацията си в блога на Boolean.getBoolean (String) и подчертах подобен проблем в тази публикация, свързан с използването на конструктора на BigDecimal, който приема двойно.

На пръв поглед може да изглежда, че конструкторът BigDecimal, който приема Java двойник, ще го държи с първоначално определената точност във всички случаи. Съобщението Javadoc за този конструктор обаче изрично предупреждава: "Резултатите от този конструктор могат да бъдат донякъде непредсказуеми." По-нататък се обяснява защо (дубълът не може да държи точната точност и това става очевидно, когато се предава на конструктора BigDecimal) и се предлага вместо него да се използва алтернативният конструктор, приемащ String като параметър. Документацията също така предлага използването на BigDecimal.valueOf (double) като предпочитан начин за конвертиране на double или float в BigDecimal.

Следният списък с кодове се използва за демонстриране на тези принципи и няколко свързани идеи.

DoubleToBigDecimal.java

import java.math.BigDecimal; import static java.lang.System.out; /** * Simple example of problems associated with using BigDecimal constructor * accepting a double. * * //marxsoftware.blogspot.com/ */ public class DoubleToBigDecimal { private final static String NEW_LINE = System.getProperty("line.separator"); public static void main(final String[] arguments) { // // Demonstrate BigDecimal from double // final double primitiveDouble = 0.1; final BigDecimal bdPrimDoubleCtor = new BigDecimal(primitiveDouble); final BigDecimal bdPrimDoubleValOf = BigDecimal.valueOf(primitiveDouble); final Double referenceDouble = Double.valueOf(0.1); final BigDecimal bdRefDoubleCtor = new BigDecimal(referenceDouble); final BigDecimal bdRefDoubleValOf = BigDecimal.valueOf(referenceDouble); out.println("Primitive Double: " + primitiveDouble); out.println("Reference Double: " + referenceDouble); out.println("Primitive BigDecimal/Double via Double Ctor: " + bdPrimDoubleCtor); out.println("Reference BigDecimal/Double via Double Ctor: " + bdRefDoubleCtor); out.println("Primitive BigDecimal/Double via ValueOf: " + bdPrimDoubleValOf); out.println("Reference BigDecimal/Double via ValueOf: " + bdRefDoubleValOf); out.println(NEW_LINE); // // Demonstrate BigDecimal from float // final float primitiveFloat = 0.1f; final BigDecimal bdPrimFloatCtor = new BigDecimal(primitiveFloat); final BigDecimal bdPrimFloatValOf = BigDecimal.valueOf(primitiveFloat); final Float referenceFloat = Float.valueOf(0.1f); final BigDecimal bdRefFloatCtor = new BigDecimal(referenceFloat); final BigDecimal bdRefFloatValOf = BigDecimal.valueOf(referenceFloat); out.println("Primitive Float: " + primitiveFloat); out.println("Reference Float: " + referenceFloat); out.println("Primitive BigDecimal/Float via Double Ctor: " + bdPrimFloatCtor); out.println("Reference BigDecimal/Float via Double Ctor: " + bdRefFloatCtor); out.println("Primitive BigDecimal/Float via ValueOf: " + bdPrimFloatValOf); out.println("Reference BigDecimal/Float via ValueOf: " + bdRefFloatValOf); out.println(NEW_LINE); // // More evidence of issues casting from float to double. // final double primitiveDoubleFromFloat = 0.1f; final Double referenceDoubleFromFloat = new Double(0.1f); final double primitiveDoubleFromFloatDoubleValue = new Float(0.1f).doubleValue(); out.println("Primitive Double from Float: " + primitiveDoubleFromFloat); out.println("Reference Double from Float: " + referenceDoubleFromFloat); out.println("Primitive Double from FloatDoubleValue: " + primitiveDoubleFromFloatDoubleValue); // // Using String to maintain precision from float to BigDecimal // final String floatString = String.valueOf(new Float(0.1f)); final BigDecimal bdFromFloatViaString = new BigDecimal(floatString); out.println("BigDecimal from Float via String.valueOf(): " + bdFromFloatViaString); } } 

Резултатът от изпълнението на горния код е показан в следващата екранна снимка.

Както показва изходът по-горе, проблемът с преместването на float на двойно пречи на човек да запази желаната точност при предаване на float директно към BigDecimal.valueOf(double)метода. String може да се използва като посредник за постигане на това, показано в примера и както е показано по подобен начин в Преобразуване на Float в Double по не толкова често срещан начин.

Имайте предвид, че тежката имплицитна употреба на BigDecimal на Groovy променя играта малко, когато използвате Groovy и динамично писане. Може да засегна това в бъдеща публикация в блога. За повече подробности относно проблемите с плаваща запетая (и подчертавам „подробности“) вижте Какво трябва да знае всеки компютърен учен за аритметиката с плаваща запетая.

Тази история „Внимание: двойно към десетично в Java“ първоначално е публикувана от JavaWorld.