I'd like to round my large double so the first thing I decided to do, was to convert it into a BigDecimal in the following way.
BigDecimal amount = BigDecimal
.valueOf(getAmount())
.setScale(2, RoundingMode.HALF_UP);
System.out.println(amount);
In my example, getAmount() returns 123456789123123424113.31.
Therefore, I expect the exact same value to be printed out by my snippet.
Instead, I get the following value:
123456789123123430000.00
Can someone explain why BigDecimal is returning an approximation of my double?
In my example, getAmount() returns 123456789123123424113.31.
No, it does not. That is not a value that a double can represent exactly.
You can easily verify that with this code:
double d = 123456789123123424113.31d;
System.out.println(d);
Which outputs
1.2345678912312343E20
This value has the minimum amount of digits to uniquely distinguish it from any other double value. Meaning that there aren't any more relevant digits in that double. You've already lost the precision before converting the value to BigDecimal.
While an integer data type such as long and int can exactly represent every (integer) value within its range, the same can't be said about floating point numbers: they have an immense range of values that they can represent, but at the cost of not being able to represent every possible value within the range. Effectively there's a limited number of digits that a floating point number can represent (about 16 decimal digits for double and about 7 decimal digits for float). Everything else will be cut off.
If you need arbitrary precision then something like BigDecimal can help: it will allocate as much memory as necessary to hold all digits (or round according to your specification, if required), making it much more complex but also more powerful.
BigDecimal bd = new BigDecimal("123456789123123424113.31");
System.out.println(bd);
will print
123456789123123424113.31
Make sure not to initialize the BigDecimal from a double value, as you'll only get the cut-off value even then.
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.
At first glance, my question could seem a bit odd; but casting a double to a float value is not what i want. Since when you cast it, you just lose some precision with respect to the rules defined IEEE-754 and can't achieve actual mapping of a double value to the range of float; it is useless. The following expression works, but it is very very expensive when you have a great amount of input:
float mappedVal = (float)((val * MAX_FLOAT_VALUE + 1) / MAX_DOUBLE_VALUE);
Can I approximate the result to the "mappedVal" mentioned above via some sort of bitwise operations to speed-up the very same computation?
I'm not sure what you're trying to achieve, since some double values are far outside the range of float.
But if you're willing to risk losing values that are too big for float, try this:
float f = new Double(val).floatValue();
Edit: which is exactly the same as casting to float. :)
This maps a double precision value into the float that has the same highest 32 bits:
float mappedVal = Float.intBitsToFloat((int)(Double.doubleToLongBits(val)>>32));
The arithmetic interpretation of this operation is a little complicated though, parts of the exponent are mapped into the mantissa...
I am trying to get percentage but the result is error, i have expression as:
Uper=(Upcount/total)*100;
where Uper is float while Upcount and total is integer i am getting the result Uper=0.
An int divided by an int will result in an int. That could be 0. Multiply 0 * 100, convert to float, and the result is still 0.0. You need at least one of the operands to be floating point before the division will give a floating point result.
Try:
Uper = ((float)Upcount/(float)total)*100.0;
The extra (float) is me being paranoid that this line might be modified in the future without fully understanding the floating-point requirement. The 100.0 is to be explicit about what you want -- a floating point result.
Perhaps changing Upcount or total to float would make more sense.
the division of 2 integers will always result in an integer which is 0 in your case.
To solve this, use the following code:
Uper = ((Double) Upcount) / total * 100
Casting at least 1 member to Double or Float will get the result you want
I have a problem with dividing a long value by 1000 and round it to an integer.
My long value is: 1313179440000
My code is
long modificationtime = 1313179440000;
Math.round(modificationtime/1000l)
If i print out the divided and formated value, it returns me:
1313179392
so.
value : 1313179440000
expected: 1313179440
got : 1313179392
I do not know why this happens.
Can anybody help me?
best regards,
prdatur
Math.round(float) is being used. A float has a larger range than a long, but it cannot represent all integers within that range -- in this case the integer 1313179440 (the original result of the division) lies in the part of the range that exceeds integer precision.
Don't use Math.round as it's not needed (input is already an integer!), or;
Use Math.round(double), as in: Math.round(modificationTime/1000d). Note that the divisor is a double and thus the dividend (and the result) of the expression are also promoted to double.
Happy coding.
The reason you get that result is that Math.Round() accepts either a double. Since your number isn't exactly representable as a double, the closest number that is gets passed in.
Note that round() is completely unnecessary here. modificationTime/1000l requires no rounding. If you do require rounding, change the argument to modificationTime/1000d.