I'm writing a bank program with a variable long balance to store cents in an account. When users inputs an amount I have a method to do the conversion from USD to cents:
public static long convertFromUsd (double amountUsd) {
if(amountUsd <= maxValue || amountUsd >= minValue) {
return (long) (amountUsd * 100.0)
} else {
//no conversion (throws an exception, but I'm not including that part of the code)
}
}
In my actual code I also check that amountUsd does not have more than 2 decimals, to avoid inputs that cannot be accurately be converted (e.g 20.001 dollars is not exactly 2000 cents). For this example code, assume that all inputs has 0, 1 or 2 decimals.
At first I looked at Long.MAX_VALUE (9223372036854775807 cents) and assumed that double maxValue = 92233720368547758.07 would be correct, but it gave me rounding errors for big amounts:
convertFromUsd(92233720368547758.07) gives output 9223372036854775807
convertFromUsd(92233720368547758.00) gives the same output 9223372036854775807
What should I set double maxValue and double minValue to always get accurate return values?
You could use BigDecimal as a temp holder
If you have a very large double (something between Double.MAX_VALUE / 100.0 + 1 and Double.MAX_VALUE) the calculation of usd * 100.0 would result in an overflow of your double.
But since you know that every possible result of <any double> * 100 will fit in a long you could use a BigDecimal as a temporary holder for your calculation.
Also, the BigDecimal class defines two methods which come in handy for this purpose:
BigDecimal#movePointRight
BigDecimal#longValueExact
By using a BigDecimal you don't have to bother about specifying a max-value at all -> any given double representing USD can be converted to a long value representing cents (assuming you don't have to handle cent-fractions).
double usd = 123.45;
long cents = BigDecimal.valueOf(usd).movePointRight(2).setScale(0).longValueExact();
Attention: Keep in mind that a double is not able to store the exact USD information in the first place. It is not possible to restore the information that has been lost by converting the double to a BigDecimal.
The only advantage a temporary BigDecimal gives you is that the calculation of usd * 100 won't overflow.
First of all, using double for monetary amounts is risky.
TL;DR
I'd recommend to stay below $17,592,186,044,416.
The floating-point representation of numbers (double type) doesn't use decimal fractions (1/10, 1/100, 1/1000, ...), but binary ones (e.g. 1/128, 1/256). So, the double number will never exactly hit something like $1.99. It will be off by some fraction most of the time.
Hopefully, the conversion from decimal digit input ("1.99") to a double number will end up with the closest binary approximation, being a tiny fraction higher or lower than the exact decimal value.
To be able to correctly represent the 100 different cent values from $xxx.00 to $xxx.99, you need a binary resolution where you can at least represent 128 different values for the fractional part, meaning that the least significant bit corresponds to 1/128 (or better), meaning that at least 7 trailing bits have to be dedicated to the fractional dollars.
The double format effectively has 53 bits for the mantissa. If you need 7 bits for the fraction, you can devote at most 46 bits to the integral part, meaning that you have to stay below 2^46 dollars ($70,368,744,177,664.00, 70 trillions) as the absolute limit.
As a precaution, I wouldn't trust the best-rounding property of converting from decimal digits to double too much, so I'd spend two more bits for the fractional part, resulting in a limit of 2^44 dollars, $17,592,186,044,416.
Code Warning
There's a flaw in your code:
return (long) (amountUsd * 100.0);
This will truncate down to the next-lower cent if the double value lies between two exact cents, meaning that e.g. "123456789.23" might become 123456789.229... as a double and getting truncated down to 12345678922 cents as a long.
You should better use
return Math.round(amountUsd * 100.0);
This will end up with the nearest cent value, most probably being the "correct" one.
EDIT:
Remarks on "Precision"
You often read statements that floating-point numbers aren't precise, and then in the next sentence the authors advocate BigDecimal or similar representations as being precise.
The validity of such a statement depends on the type of number you want to represent.
All the number representation systems in use in today's computing are precise for some types of numbers and imprecise for others. Let's take a few example numbers from mathematics and see how well they fit into some typical data types:
42: A small integer can be represented exactly in virtually all types.
1/3: All the typical data types (including double and BigDecimal) fail to represent 1/3 exactly. They can only do a (more or less close) approximation. The result is that multiplication with 3 does not exactly give the integer 1. Few languages offer a "ratio" type, capable to represent numbers by numerator and denominator, thus giving exact results.
1/1024: Because of the power-of-two denominator, float and double can easily do an exact representation. BigDecimal can do as well, but needs 10 fractional digits.
14.99: Because of the decimal fraction (can be rewritten as 1499/100), BigDecimal does it easily (that's what it's made for), float and double can only give an approximation.
PI: I don't know of any language with support for irrational numbers - I even have no idea how this could be possible (aside from treating popular irrationals like PI and E symbolically).
123456789123456789123456789: BigInteger and BigDecimal can do it exactly, double can do an approximation (with the last 13 digits or so being garbage), int and long fail completely.
Let's face it: Each data type has a class of numbers that it can represent exactly, where computations deliver precise results, and other classes where it can at best deliver approximations.
So the questions should be:
What's the type and range of numbers to be represented here?
Is an approximation okay, and if yes, how close should it be?
What's the data type that matches my requirements?
Using a double, the biggest, in Java, would be: 70368744177663.99.
What you have in a double is 64 bit (8 byte) to represent:
Decimals and integers
+/-
Problem is to get it to not round of 0.99 so you get 46 bit for the integer part and the rest need to be used for the decimals.
You can test with the following code:
double biggestPossitiveNumberInDouble = 70368744177663.99;
for(int i=0;i<130;i++){
System.out.printf("%.2f\n", biggestPossitiveNumberInDouble);
biggestPossitiveNumberInDouble=biggestPossitiveNumberInDouble-0.01;
}
If you add 1 to biggestPossitiveNumberInDouble you will see it starting to round off and lose precision.
Also note the round off error when subtracting 0.01.
First iterations
70368744177663.99
70368744177663.98
70368744177663.98
70368744177663.97
70368744177663.96
...
The best way in this case would not to parse to double:
System.out.println("Enter amount:");
String input = new Scanner(System.in).nextLine();
int indexOfDot = input.indexOf('.');
if (indexOfDot == -1) indexOfDot = input.length();
int validInputLength = indexOfDot + 3;
if (validInputLength > input.length()) validInputLength = input.length();
String validInput = input.substring(0,validInputLength);
long amout = Integer.parseInt(validInput.replace(".", ""));
System.out.println("Converted: " + amout);
This way you don't run into the limits of double and just have the limits of long.
But ultimately would be to go with a datatype made for currency.
You looked at the largest possible long number, while the largest possible double is smaller. Calculating (amountUsd * 100.0) results in a double (and afterwards gets casted into a long).
You should ensure that (amountUsd * 100.0) can never be bigger than the largest double, which is 9007199254740992.
Floating values (float, double) are stored differently than integer values (int, long) and while double can store very large values, it is not good for storing money amounts as they get less accurate the bigger or more decimal places the number has.
Check out How many significant digits do floats and doubles have in java? for more information about floating point significant digits
A double is 15 significant digits, the significant digit count is the total number of digits from the first non-zero digit. (For a better explanation see https://en.wikipedia.org/wiki/Significant_figures Significant figures rules explained)
Therefor in your equation to include cents and make sure you are accurate you would want the maximum number to have no more than 13 whole number places and 2 decimal places.
As you are dealing with money it would be better not to use floating point values. Check out this article on using BigDecimal for storing currency: https://medium.com/#cancerian0684/which-data-type-would-you-choose-for-storing-currency-values-like-trading-price-dd7489e7a439
As you mentioned users are inputting an amount, you could read it in as a String rather than a floating point value and pass that into a BigDecimal.
I've always been told never to represent money with double or float types, and this time I pose the question to you: why?
I'm sure there is a very good reason, I simply do not know what it is.
Because floats and doubles cannot accurately represent the base 10 multiples that we use for money. This issue isn't just for Java, it's for any programming language that uses base 2 floating-point types.
In base 10, you can write 10.25 as 1025 * 10-2 (an integer times a power of 10). IEEE-754 floating-point numbers are different, but a very simple way to think about them is to multiply by a power of two instead. For instance, you could be looking at 164 * 2-4 (an integer times a power of two), which is also equal to 10.25. That's not how the numbers are represented in memory, but the math implications are the same.
Even in base 10, this notation cannot accurately represent most simple fractions. For instance, you can't represent 1/3: the decimal representation is repeating (0.3333...), so there is no finite integer that you can multiply by a power of 10 to get 1/3. You could settle on a long sequence of 3's and a small exponent, like 333333333 * 10-10, but it is not accurate: if you multiply that by 3, you won't get 1.
However, for the purpose of counting money, at least for countries whose money is valued within an order of magnitude of the US dollar, usually all you need is to be able to store multiples of 10-2, so it doesn't really matter that 1/3 can't be represented.
The problem with floats and doubles is that the vast majority of money-like numbers don't have an exact representation as an integer times a power of 2. In fact, the only multiples of 0.01 between 0 and 1 (which are significant when dealing with money because they're integer cents) that can be represented exactly as an IEEE-754 binary floating-point number are 0, 0.25, 0.5, 0.75 and 1. All the others are off by a small amount. As an analogy to the 0.333333 example, if you take the floating-point value for 0.01 and you multiply it by 10, you won't get 0.1. Instead you will get something like 0.099999999786...
Representing money as a double or float will probably look good at first as the software rounds off the tiny errors, but as you perform more additions, subtractions, multiplications and divisions on inexact numbers, errors will compound and you'll end up with values that are visibly not accurate. This makes floats and doubles inadequate for dealing with money, where perfect accuracy for multiples of base 10 powers is required.
A solution that works in just about any language is to use integers instead, and count cents. For instance, 1025 would be $10.25. Several languages also have built-in types to deal with money. Among others, Java has the BigDecimal class, and Rust has the rust_decimal crate, and C# has the decimal type.
From Bloch, J., Effective Java, (2nd ed, Item 48. 3rd ed, Item 60):
The float and double types are
particularly ill-suited for monetary
calculations because it is impossible
to represent 0.1 (or any other
negative power of ten) as a float or
double exactly.
For example, suppose you have $1.03
and you spend 42c. How much money do
you have left?
System.out.println(1.03 - .42);
prints out 0.6100000000000001.
The right way to solve this problem is
to use BigDecimal, int or long
for monetary calculations.
Though BigDecimal has some caveats (please see currently accepted answer).
This is not a matter of accuracy, nor is it a matter of precision. It is a matter of meeting the expectations of humans who use base 10 for calculations instead of base 2. For example, using doubles for financial calculations does not produce answers that are "wrong" in a mathematical sense, but it can produce answers that are not what is expected in a financial sense.
Even if you round off your results at the last minute before output, you can still occasionally get a result using doubles that does not match expectations.
Using a calculator, or calculating results by hand, 1.40 * 165 = 231 exactly. However, internally using doubles, on my compiler / operating system environment, it is stored as a binary number close to 230.99999... so if you truncate the number, you get 230 instead of 231. You may reason that rounding instead of truncating would have given the desired result of 231. That is true, but rounding always involves truncation. Whatever rounding technique you use, there are still boundary conditions like this one that will round down when you expect it to round up. They are rare enough that they often will not be found through casual testing or observation. You may have to write some code to search for examples that illustrate outcomes that do not behave as expected.
Assume you want to round something to the nearest penny. So you take your final result, multiply by 100, add 0.5, truncate, then divide the result by 100 to get back to pennies. If the internal number you stored was 3.46499999.... instead of 3.465, you are going to get 3.46 instead 3.47 when you round the number to the nearest penny. But your base 10 calculations may have indicated that the answer should be 3.465 exactly, which clearly should round up to 3.47, not down to 3.46. These kinds of things happen occasionally in real life when you use doubles for financial calculations. It is rare, so it often goes unnoticed as an issue, but it happens.
If you use base 10 for your internal calculations instead of doubles, the answers are always exactly what is expected by humans, assuming no other bugs in your code.
I'm troubled by some of these responses. I think doubles and floats have a place in financial calculations. Certainly, when adding and subtracting non-fractional monetary amounts there will be no loss of precision when using integer classes or BigDecimal classes. But when performing more complex operations, you often end up with results that go out several or many decimal places, no matter how you store the numbers. The issue is how you present the result.
If your result is on the borderline between being rounded up and rounded down, and that last penny really matters, you should be probably be telling the viewer that the answer is nearly in the middle - by displaying more decimal places.
The problem with doubles, and more so with floats, is when they are used to combine large numbers and small numbers. In java,
System.out.println(1000000.0f + 1.2f - 1000000.0f);
results in
1.1875
I'll risk being downvoted, but I think the unsuitability of floating point numbers for currency calculations is overrated. As long as you make sure you do the cent-rounding correctly and have enough significant digits to work with in order to counter the binary-decimal representation mismatch explained by zneak, there will be no problem.
People calculating with currency in Excel have always used double precision floats (there is no currency type in Excel) and I have yet to see anyone complaining about rounding errors.
Of course, you have to stay within reason; e.g. a simple webshop would probably never experience any problem with double precision floats, but if you do e.g. accounting or anything else that requires adding a large (unrestricted) amount of numbers, you wouldn't want to touch floating point numbers with a ten foot pole.
Floats and doubles are approximate. If you create a BigDecimal and pass a float into the constructor you see what the float actually equals:
groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375
this probably isn't how you want to represent $1.01.
The problem is that the IEEE spec doesn't have a way to exactly represent all fractions, some of them end up as repeating fractions so you end up with approximation errors. Since accountants like things to come out exactly to the penny, and customers will be annoyed if they pay their bill and after the payment is processed they owe .01 and they get charged a fee or can't close their account, it's better to use exact types like decimal (in C#) or java.math.BigDecimal in Java.
It's not that the error isn't controllable if you round: see this article by Peter Lawrey. It's just easier not to have to round in the first place. Most applications that handle money don't call for a lot of math, the operations consist of adding things or allocating amounts to different buckets. Introducing floating point and rounding just complicates things.
While it's true that floating point type can represent only approximatively decimal data, it's also true that if one rounds numbers to the necessary precision before presenting them, one obtains the correct result. Usually.
Usually because the double type has a precision less than 16 figures. If you require better precision it's not a suitable type. Also approximations can accumulate.
It must be said that even if you use fixed point arithmetic you still have to round numbers, were it not for the fact that BigInteger and BigDecimal give errors if you obtain periodic decimal numbers. So there is an approximation also here.
For example COBOL, historically used for financial calculations, has a maximum precision of 18 figures. So there is often an implicit rounding.
Concluding, in my opinion the double is unsuitable mostly for its 16 digit precision, which can be insufficient, not because it is approximate.
Consider the following output of the subsequent program. It shows that after rounding double give the same result as BigDecimal up to precision 16.
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;
}
}
The result of floating point number is not exact, which makes them unsuitable for any financial calculation which requires exact result and not approximation. float and double are designed for engineering and scientific calculation and many times doesn’t produce exact result also result of floating point calculation may vary from JVM to JVM. Look at below example of BigDecimal and double primitive which is used to represent money value, its quite clear that floating point calculation may not be exact and one should use BigDecimal for financial calculations.
// 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)));
Output:
difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9
As said earlier "Representing money as a double or float will probably look good at first as the software rounds off the tiny errors, but as you perform more additions, subtractions, multiplications and divisions on inexact numbers, you’ll lose more and more precision as the errors add up. This makes floats and doubles inadequate for dealing with money, where perfect accuracy for multiples of base 10 powers is required."
Finally Java has a standard way to work with Currency And Money!
JSR 354: Money and Currency API
JSR 354 provides an API for representing, transporting, and performing comprehensive calculations with Money and Currency. You can download it from this link:
JSR 354: Money and Currency API Download
The specification consists of the following things:
An API for handling e. g. monetary amounts and currencies
APIs to support interchangeable implementations
Factories for creating instances of the implementation classes
Functionality for calculations, conversion and formatting of monetary amounts
Java API for working with Money and Currencies, which is planned to be included in Java 9.
All specification classes and interfaces are located in the javax.money.* package.
Sample Examples of JSR 354: Money and Currency API:
An example of creating a MonetaryAmount and printing it to the console looks like this:
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));
When using the reference implementation API, the necessary code is much simpler:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
The API also supports calculations with MonetaryAmounts:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));
CurrencyUnit and MonetaryAmount
// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MonetaryAmount has various methods that allow accessing the assigned currency, the numeric amount, its precision and more:
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;
MonetaryAmounts can be rounded using a rounding operator:
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
When working with collections of MonetaryAmounts, some nice utility methods for filtering, sorting and grouping are available.
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"));
Custom MonetaryAmount operations
// 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
Resources:
Handling money and currencies in Java with JSR 354
Looking into the Java 9 Money and Currency API (JSR 354)
See Also: JSR 354 - Currency and Money
Most answers have highlighted the reasons why one should not use doubles for money and currency calculations. And I totally agree with them.
It doesn't mean though that doubles can never be used for that purpose.
I have worked on a number of projects with very low gc requirements, and having BigDecimal objects was a big contributor to that overhead.
It's the lack of understanding about double representation and lack of experience in handling the accuracy and precision that brings about this wise suggestion.
You can make it work if you are able to handle the precision and accuracy requirements of your project, which has to be done based on what range of double values is one dealing with.
You can refer to guava's FuzzyCompare method to get more idea. The parameter tolerance is the key.
We dealt with this problem for a securities trading application and we did an exhaustive research on what tolerances to use for different numerical values in different ranges.
Also, there might be situations when you're tempted to use Double wrappers as a map key with hash map being the implementation. It is very risky because Double.equals and hash code for example values "0.5" & "0.6 - 0.1" will cause a big mess.
If your computation involves various steps, arbitrary precision arithmetic won't cover you 100%.
The only reliable way to use a perfect representation of results(Use a custom Fraction data type that will batch division operations to the last step) and only convert to decimal notation in the last step.
Arbitrary precision won't help because there always can be numbers that has so many decimal places, or some results such as 0.6666666... No arbitrary representation will cover the last example. So you will have small errors in each step.
These errors will add-up, may eventually become not easy to ignore anymore. This is called Error Propagation.
Many of the answers posted to this question discuss IEEE and the standards surrounding floating-point arithmetic.
Coming from a non-computer science background (physics and engineering), I tend to look at problems from a different perspective. For me, the reason why I wouldn't use a double or float in a mathematical calculation is that I would lose too much information.
What are the alternatives? There are many (and many more of which I am not aware!).
BigDecimal in Java is native to the Java language.
Apfloat is another arbitrary-precision library for Java.
The decimal data type in C# is Microsoft's .NET alternative for 28 significant figures.
SciPy (Scientific Python) can probably also handle financial calculations (I haven't tried, but I suspect so).
The GNU Multiple Precision Library (GMP) and the GNU MFPR Library are two free and open-source resources for C and C++.
There are also numerical precision libraries for JavaScript(!) and I think PHP which can handle financial calculations.
There are also proprietary (particularly, I think, for Fortran) and open-source solutions as well for many computer languages.
I'm not a computer scientist by training. However, I tend to lean towards either BigDecimal in Java or decimal in C#. I haven't tried the other solutions I've listed, but they are probably very good as well.
For me, I like BigDecimal because of the methods it supports. C#'s decimal is very nice, but I haven't had the chance to work with it as much as I'd like. I do scientific calculations of interest to me in my spare time, and BigDecimal seems to work very well because I can set the precision of my floating point numbers. The disadvantage to BigDecimal? It can be slow at times, especially if you're using the divide method.
You might, for speed, look into the free and proprietary libraries in C, C++, and Fortran.
To add on previous answers, there is also the option of implementing Joda-Money in Java, besides BigDecimal, when dealing with the problem addressed in the question. Java module name is org.joda.money.
It requires Java SE 8 or later and has no dependencies.
To be more precise, there is a compile-time dependency but it is not
required.
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
Examples of using Joda Money:
// 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();
Documentation:
http://joda-money.sourceforge.net/apidocs/org/joda/money/Money.html
Implementation examples:
https://www.programcreek.com/java-api-examples/?api=org.joda.money.Money
Take a look at this simple example: it looks like logically correct, but in real world this can return unexpected results if not threated correctly:
0.1 x 10 = 1 👍 , so:
double total = 0.0;
// adds 10 cents, 10 times
for (int i = 0; i < 10; i++) {
total += 0.1; // adds 10 cents
}
Log.d("result: ", "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
}
// total should be equals to 0.0, right?
Log.d("result: ", "current total: " + total);
if (total == 0.0) {
Log.d("result: ", "is total equal to ZERO? YES, of course!!");
} else {
Log.d("result: ", "is total equal to ZERO? No...");
// so be careful comparing equality in this cases!!!
}
OUTPUT:
result: current total: 0.9999999999999999
result: current total: 2.7755575615628914E-17 🤔
result: is total equal to ZERO? No... 😌
Float is binary form of Decimal with different design; they are two different things. There are little errors between two types when converted to each other. Also, float is designed to represent infinite large number of values for scientific. That means it is designed to lost precision to extreme small and extreme large number with that fixed number of bytes. Decimal can't represent infinite number of values, it bounds to just that number of decimal digits. So Float and Decimal are for different purpose.
There are some ways to manage the error for currency value:
Use long integer and count in cents instead.
Use double precision, keep your significant digits to 15 only so decimal can be exactly simulated. Round before presenting values; Round often when doing calculations.
Use a decimal library like Java BigDecimal so you don't need to use double to simulate decimal.
p.s. it is interesting to know that most brands of handheld scientific calculators works on decimal instead of float. So no one complaint float conversion errors.
American currency can easily be represented with dollar and cent amounts. Integers are 100% precise, while floating point binary numbers do not exactly match floating point decimals.
I have a float-based storage of decimal by their nature numbers. The precision of float is fine for my needs. Now I want is to perform some more precise calculations with these numbers using double.
An example:
float f = 0.1f;
double d = f; //d = 0.10000000149011612d
// but I want some code that will convert 0.1f to 0.1d;
Update 1:
I know very well that 0.1f != 0.1d. This question is not about precise decimal calculations. Sadly, the question was downvoted. I will try to explain it again...
Let's say I work with an API that returns float numbers for decimal MSFT stock prices. Believe or not, this API exists:
interface Stock {
float[] getDayPrices();
int[] getDayVolumesInHundreds();
}
It is known that the price of a MSFT share is a decimal number with no more than 5 digits, e.g. 31.455, 50.12, 45.888. Obviously the API does not work with BigDecimal because it would be a big overhead for the purpose to just pass the price.
Let's also say I want to calculate a weighted average of these prices with double precision:
float[] prices = msft.getDayPrices();
int[] volumes = msft.getDayVolumesInHundreds();
double priceVolumeSum = 0.0;
long volumeSum = 0;
for (int i = 0; i < prices.length; i++) {
double doublePrice = decimalFloatToDouble(prices[i]);
priceVolumeSum += doublePrice * volumes[i];
volumeSum += volumes[i];
}
System.out.println(priceVolumeSum / volumeSum);
I need a performant implemetation of decimalFloatToDouble.
Now I use the following code, but I need a something more clever:
double decimalFloatToDouble(float f) {
return Double.parseDouble(Float.toString(f));
}
EDIT: this answer corresponds to the question as initially phrased.
When you convert 0.1f to double, you obtain the same number, the imprecise representation of the rational 1/10 (which cannot be represented in binary at any precision) in single-precision. The only thing that changes is the behavior of the printing function. The digits that you see, 0.10000000149011612, were already there in the float variable f. They simply were not printed because these digits aren't printed when printing a float.
Ignore these digits and compute with double as you wish. The problem is not in the conversion, it is in the printing function.
As I understand you, you know that the float is within one float-ulp of an integer number of hundredths, and you know that you're well inside the range where no two integer numbers of hundredths map to the same float. So the information isn't gone at all; you just need to figure out which integer you had.
To get two decimal places, you can multiply by 100, rint/Math.round the result, and multiply by 0.01 to get a close-by double as you wanted. (To get the closest, divide by 100.0 instead.) But I suspect you knew this already and are looking for something that goes a little faster. Try ((9007199254740992 + 100.0 * x) - 9007199254740992) * 0.01 and don't mess with the parentheses. Maybe strictfp that hack for good measure.
You said five significant figures, and apparently your question isn't limited to MSFT share prices. Up until doubles can't represent powers of 10 exactly, this isn't too bad. (And maybe this works beyond that threshold too.) The exponent field of a float narrows down the needed power of ten down to two things, and there are 256 possibilities. (Except in the case of subnormals.) Getting the right power of ten just needs a conditional, and the rounding trick is straightforward enough.
All of this is all going to be a mess, and I'd recommend you stick with the toString approach for all the weird cases.
If your goal is to have a double whose canonical representation will match the canonical representation of a float converting the float to string and converting the result back to double would probably be the most accurate way of achieving that result, at least when it's possible (I don't know for certain whether Java's double-to-string logic would guarantee that there won't be a pair of consecutive double values which report themselves as just above and just-below a number with five significant figures).
If your goal is to round to five significant figures a value which is known to have been rounded to five significant figures while in float form, I would suggest that the simplest approach is probably to simply round to five significant figures. If your magnitude of your numbers will be roughly within the range 1E+/-12, start by finding the smallest power of ten which is smaller than your number, multiply that by 100,000, multiply your number by that, round to the nearest unit, and divide by that power of ten. Because division is often much slower than multiplication, if performance is critical, you might keep a table with powers of ten and their reciprocals. To avoid the possibility of rounding errors, your table should store for each power of then the closest power-of-two double to its reciprocal, and then the closest double to the difference between the first double and the actual reciprocal. Thus, the reciprocal of 100 would be stored as 0.0078125 + 0.0021875; the value n/100 would be computed as n*0.0078125 + n*0.0021875. The first term would never have any round-off error (multiplying by a power of two), and the second value would have precision beyond that needed for the final result, so the final result should thus be rounded accurately.
I have seen a very weird behaviour in Java's double variable, as I'm trying to simply add small fractions to a double and I see a completely bizarre results.
double test = 0;
test += 0.71;
test += 0.2;
Now I'd expect the result to be:
test = 0.91
Right? Wrong!
In reality, this is the number I get in my test double:
test = 0.9099999999999999
Now while this is very close, it's a very bizarre fraction loss, and in the long run it causes serious bugs in my program.
With a float I've gotten even a weirder result.
Any help would be greatly appreciated.
Thanks
There is nothing bizarre about it at all. 0.91, 0.71 and 0.2 are not representable as a IEEE754 floating point values as they would have a recurring fractional part when represented in binary. The situation is entirely analogous to trying to represent 1/3 in base 10 with a finite number of digits. You can't do it.
What you are seeing is a rounding error that is normal when doing floating point calculations. You have to code around it. So for instance, you can't reliably compare for equality, you have to see the two numbers are within some small delta of each other. For a slightly more in depth but still understandable explanation see The Floating Point Guide.
That's the magic of binary encoding of floating point values (look for IEEE754 : http://en.wikipedia.org/wiki/IEEE_754-2008 ). If you want to be sure to never have this kind of things, you're maybe looking for BigDecimal :
http://docs.oracle.com/javase/1.5.0/docs/api/java/math/BigDecimal.html
Basic rules :
don't use equality tests when dealing with floating point numbers (you must test gaps)
round numbers you're displaying (usually using DecimalFormat)
don't use floating point numbers for financial applications
the float is generally the way to go for scientific or industrial operations, as long as you understand IEEE754
double can only approximate most fractional values. This means you need to use some rounding if you want to get your expect result. Or you can use BigDecimal which takes care of this issue for you.
double test = 0;
test += 0.71;
test += 0.2;
System.out.printf("%.2f%n", test);
prints
0.91
For your own interest
System.out.println("0.71 is actually " + new BigDecimal(0.71));
System.out.println("0.2 is actually " + new BigDecimal(0.2));
System.out.println("0.71+0.2 is actually " + new BigDecimal(0.71 + 0.2));
System.out.println("0.91 is actually " + new BigDecimal(0.91));
System.out.println("0.71+0.2 == 0.91 is " + (0.71 + 0.2 == 0.91));
prints
0.71 is actually 0.70999999999999996447286321199499070644378662109375
0.2 is actually 0.200000000000000011102230246251565404236316680908203125
0.71+0.2 is actually 0.9099999999999999200639422269887290894985198974609375
0.91 is actually 0.91000000000000003108624468950438313186168670654296875
0.71+0.2 == 0.91 is false
Java uses something called floating-point to represent decimals. They use exponential notation. Here's what I mean:
There is a multiplier (M), and an exponent between 1023 and -1022 (E).
A number (N) is represented like this: M * 2^E.
4.25 is represented like this:
17 * 2^-2.
0.91 cannot be represented in base 2 exactly, but Java can get pretty close:
0.909999999999..
Therefore, it is impossible to accurately add these numbers together.
In brief - I am having a hard time with the float left overs
(i.e. 10.00000123 instead of 10)
Here I go :
I need to generate list of floats with constant gap as follows
(actually its a map , I need to retreive the object nut nevermind that)
List A: 0.25, 0.5, 0.75, 1, ...
or
List B: 0.01, 0.02, 0.03, 0.04, ...
every time I get a number and I round it to the neerest cell in the list.
lets say I get 0.051 to retreive a cell in list A - I return 0.05.
lets say I get 0.21 to retreive a cell in list B - I return 0.25.
So I started be doing this
float a = Math.round(Value / step) * step;
but than I get a lot of time 0.2500001 (float leftovers )
I need a smart way to round it .
Maybe by taking the number of digits after the dot and doing again
Math.round(Value / 100) * 100;?
Is there a smarter way?
I tried doig this
final float factor = Math.round(1 / step);
final float value = (float) Math.round(value * factor) / factor;
but I sometimes have a list like this
List A: 10, 15 , 20, 25, 30, ...
and when I get 22 I retreive the cell of 20.
the problem is that When I get a gap of 10
Math.round(1 / baseAssetStep)
returns 0 - and I get NaN
Use BigDecimal instead of float.
From the Java Tutorials of Primitive Data Types:
float: [...] This data type should never be used for precise values, such as currency. For that, you will need to use the
java.math.BigDecimal class instead. Numbers and Strings covers
BigDecimal and other useful classes provided by the Java platform.
Firstly, I would use double or long instead as these have much more digits of accuracy. If you really need to, use BigDecimal, but its pretty rare to find a real world situation where double or long would not do the job.
double d = 10.00000123;
double r = Math.round(d * 10000) / 10000.0;
or using long with fixed point precision.
long l = 100000; // the actual value * 10000
A common use case for fixed point precision is money. Instead of using dollars with double use cents with long or even int instead.
In short, "these are not the numbers you're looking for."
Floating points are represented as binary fractional numbers, and some numbers that can be easily represented in base-10 (0.01, for instance) can't be represented with a finite number of binary digits. This is similar to how 1/3 is easy in base 3 (it's just 0.1), but requires an infinite number of digits in base 10 (0.333...).
If you tried to represent 1/3 with a finite number of digits in base 10, you'd get an approximation. Similarly, if you try to represent 1/10 with a finite number of digits in base 2 (which is what float and double do), you'll get an approximation, and similarly with 1/100. What you think is 0.01 in the code is actually a number that's very close, but not exactly equal to, 1/100.
There are many resources out there concerning floating points and the difficulty in working with them. http://floating-point-gui.de/ is a good place to start.
you need a constant gap, so maybe try another solution. Take your gap, let's name it G. Draw a randow int - let's call it R, (if you know maximum number in your list you can draw it properly). Now the only thing to do would be R*G which will give you a number from your list - of course with some precision because this is inevitable using float - to print it just use format. You can also combine this with BigDecimal ;)