I have weird decimal calculation that really surprise me, I have two big decimal number, one is a normal proce and the other one is an offer price. Then I want to calculate discount percentage and ceil it to the nearest integer.
Here are the code:
BigDecimal listPrice = new BigDecimal(510000);
BigDecimal offerPrice = new BigDecimal(433500);
int itemDiscount = (int)Math.ceil(((listPrice.longValue() - offerPrice.longValue()) / (float) listPrice.longValue()) * 100);
I expect it would set 15 as value of itemDiscount, but surprisingly it has 16, wow. Then i print each calculation to show in which statement is the problem, so i put System.out.println for each statement as below :
System.out.println(listPrice.longValue() - offerPrice.longValue()); //==> show 76500
System.out.println((listPrice.longValue() - offerPrice.longValue()) / (float) listPrice.longValue()); // ==> 0.15
System.out.println((listPrice.longValue() - offerPrice.longValue()) * 100 / (float) listPrice.longValue()); // ==> 15.000001
the problem is in above statement, istead of returning 15.0, it return 15.000001. And when i ceil it, it will of course return 16 instead of 15.
What is the explanation if this case? is this the way it is or it is a bug?
What is the explanation if this case? is this the way it is or it is a bug?
It is the way it is. It is not a bug.
You are doing the calculation using floating point types (float) and floating point arithmetic is imprecise.
I'm not sure what the best fix is here. Maybe doing the computation using BigDecimal arithmetic methods would give a better result, but it is by no means guaranteed that you won't get similar problems in this calculation with different inputs ...
However, I suspect that the real problem is that you should not be using ceil in that calculation. Even BigDecimal will give you rounding errors; e.g. if your computation involves dividing by 3, the intermediate result cannot be precisely represented using a base-10 representation.
The correct way to do calculations using Real numbers is to properly take account of the error bars in the calculation.
Try using the divide method directly from the BigDecimal class. If you are casting to a float then you are not using the benefit of BigDecimal .
http://www.roseindia.net/java/java-bigdecimal/bigDecimal-divide-int.shtml
Related
I was writing some converters of units using BigDecimals and I ran across a situation where I had to multiply a number with a fraction - periodic number.
For most cases the precision is good enough, but lets say we have an equation like:
BigDecimal.valueOf(90)
.multiply(BigDecimal.valueOf(10)
.divide(BigDecimal.valueOf(90), 6, RoundingMode.HALF_UP))
Normally this would equal 10, however because of rounding, we will get 9.999999...
Is there an elegant way of implementing this without having an if condition detecting when the fraction can be cut?
The following will work:
BigDecimal.valueOf(90)
.multiply(BigDecimal.valueOf(10))
.divide(BigDecimal.valueOf(90), 6, RoundingMode.HALF_UP)
The difference is that here the operations are chained, which allows for resolving such cases. In your solution the division needs to be calculated (where error occurs) and then multiplication, because it's passed as argument.
Do not know if this will be a general case answer for you, but it works in the example given:
bd = BigDecimal.valueOf(90)
.multiply(BigDecimal.valueOf(10))
.divide(BigDecimal.valueOf(90));
Multiply by 10 then divide by 90.
a * x = ax
- --
z z
You will need to include some rounding logic for rational numbers:
bd = BigDecimal.valueOf(1)
.multiply(BigDecimal.valueOf(1))
.divide(BigDecimal.valueOf(3));
Will fail without rounding.
I'm wondering what the best way to fix precision errors is in Java. As you can see in the following example, there are precision errors:
class FloatTest
{
public static void main(String[] args)
{
Float number1 = 1.89f;
for(int i = 11; i < 800; i*=2)
{
System.out.println("loop value: " + i);
System.out.println(i*number1);
System.out.println("");
}
}
}
The result displayed is:
loop value: 11
20.789999
loop value: 22
41.579998
loop value: 44
83.159996
loop value: 88
166.31999
loop value: 176
332.63998
loop value: 352
665.27997
loop value: 704
1330.5599
Also, if someone can explain why it only does it starting at 11 and doubling the value every time. I think all other values (or many of them at least) displayed the correct result.
Problems like this have caused me headache in the past and I usually use number formatters or put them into a String.
Edit: As people have mentioned, I could use a double, but after trying it, it seems that 1.89 as a double times 792 still outputs an error (the output is 1496.8799999999999).
I guess I'll try the other solutions such as BigDecimal
If you really care about precision, you should use BigDecimal
https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/math/BigDecimal.html
The problem is not with Java but with the good standard float's (http://en.wikipedia.org/wiki/IEEE_floating-point_standard).
You can either:
use Double and have a bit more precision (but not perfect of course, it also has limited precision)
use a arbitrary-precision-library
use numerically stable algorithms and truncate/round digits of which you are not sure they are correct (you can calculate numeric precision of operations)
When you print the result of a double operation you need to use appropriate rounding.
System.out.printf("%.2f%n", 1.89 * 792);
prints
1496.88
If you want to round the result to a precision, you can use rounding.
double d = 1.89 * 792;
d = Math.round(d * 100) / 100.0;
System.out.println(d);
prints
1496.88
However if you see below, this prints as expected, as there is a small amount of implied rounding.
It worth nothing that (double) 1.89 is not exactly 1.89 It is a close approximation.
new BigDecimal(double) converts the exact value of double without any implied rounding. It can be useful in finding the exact value of a double.
System.out.println(new BigDecimal(1.89));
System.out.println(new BigDecimal(1496.88));
prints
1.8899999999999999023003738329862244427204132080078125
1496.8800000000001091393642127513885498046875
Most of your question has been pretty well covered, though you might still benefit from reading the [floating-point] tag wiki to understand why the other answers work.
However, nobody has addressed "why it only does it starting at 11 and doubling the value every time," so here's the answer to that:
for(int i = 11; i < 800; i*=2)
╚═══╤════╝ ╚╤═╝
│ └───── "double the value every time"
│
└───── "start at 11"
You could use doubles instead of floats
If you really need arbitrary precision, use BigDecimal.
first of Float is the wrapper class for the primitive float
and doubles have more precision
but if you only want to calculate down to the second digit (for monetary purposes for example) use an integer (as if you are using cents as unit) and add some scaling logic when you are multiplying/dividing
or if you need arbitrary precision use BigDecimal
If precision is vital, you should use BigDecimal to make sure that the required precision remains. When you instantiate the calculation, remember to use strings to instantiate the values instead of doubles.
I never had a problem with simple arithmetic precision in either Basic, Visual Basic, FORTRAN, ALGOL or other "primitive" languages. It is beyond comprehension that JAVA can't do simple arithmetic without introducing errors. I need just two digits to the right of the decimal point for doing some accounting. Using Float subtracting 1000 from 1355.65 I get 355.650002! In order to get around this ridiculous error I have implemented a simple solution. I process my input by separating the values on each side of the decimal point as character, convert each to integers, multiply each by 1000 and add the two back together as integers. Ridiculous but there are no errors introduced by the poor JAVA algorithms.
Why does the following code:
System.out.println((int)(19.99 * 100));
produce the result "1998"?
Rounding errors. If you look at the result of your calculation without the cast, you get:
1998.9999999999998
So when you cast to int, the decimal part is dropped, not rounded up, and you get 1998.
The moral is, if you need an exact answer, don't use float / double at all. If you're talking about a discrete value like money, use int and deal with the atomic unit (eg. pence.) If you do need exact decimals, then BigDecimal is your friend.
While you can bodge the result here using Math.round() to bring the result to where it's expected, this won't work in all cases and fails to address the underlying issue.
That is because 19.99 cannot be represented exactly.
System.out.println(new BigDecimal(19.99));
prints the value this actually represents which is the closest to 19.99 it can represent.
19.989999999999998436805981327779591083526611328125
and 19.99 * 100 is
System.out.println(new BigDecimal(19.99 * 100));
which is
1998.999999999999772626324556767940521240234375
The problem is that you have a representation error in 19.99 which is still there when multiplied by 100 you get a number which is slightly too small.
if you multiply by 100 and round down which is what (int) does you should expect to get 1998.
An alternative is
System.out.println(Math.round(19.99 * 100));
because the calculation of 19.99 * 100 will result in 1998.999999 and you are casting it to int it will discard the fractional part of it.
It is due to a rounding issues. double and float are prone to these issues, which is why it is recommended you use the BigDecimal class.
This code should print what is expected:
BigDecimal bg = new BigDecimal("19.99");
System.out.println(bg.multiply(new BigDecimal("10")));
This yields:
199.90
System.out.println((19.99 * 100));
produces the result 1998.9999999999998 by adding int casting it truncates the fraction part and returns 1998
Floating point data types (float and double in Java) can only approximately represent most decimal values. See Joshua Bloch's words of wisdom on the subject for more details.
Is this a glitch in Java?
I go to solve this expression: 3.1 - 7.1
I get the answer: -3.9999999999999996
What is going on here?
A great explanation can be found here. http://www.ibm.com/developerworks/java/library/j-jtp0114/
Floating point arithmetic is rarely exact. While some numbers, such
as 0.5, can be exactly represented as a binary (base 2) decimal (since
0.5 equals 2-1), other numbers, such as 0.1, cannot be. As a result, floating point operations may result in rounding errors, yielding a
result that is close to -- but not equal to -- the result you might
expect. For example, the simple calculation below results in
2.600000000000001, rather than 2.6:
double s=0;
for (int i=0; i<26; i++)
s += 0.1;
System.out.println(s);
Similarly, multiplying .1*26 yields a result different from that of
adding .1 to itself 26 times. Rounding errors become even more serious
when casting from floating point to integer, because casting to an
integral type discards the non-integral portion, even for calculations
that "look like" they should have integral values. For example, the
following statements:
double d = 29.0 * 0.01;
System.out.println(d);
System.out.println((int) (d * 100));
will produce as output:
0.29
28
which is probably not what you might expect at first.
See the provided reference for more information.
As mentioned by several others you cannot count on double if you would like to get an exact decimal value, e.g. when implementing monetary applications. What you should do instead is to take a closer look at BigDecimal:
BigDecimal a = new BigDecimal("3.1");
BigDecimal b = new BigDecimal("7.1");
BigDecimal result = a.subtract(b);
System.out.println(result); // Prints -4.0
Computers are 100% so in the math world that is correct, to the average person it is not. Java cant have a error on a specific number as it is just code that runs the same way but has a different input!
P.S. Google how to round a number
rounding errors in floating points
same way that 3 * 0.1 != 0.3 (when it's not folded by the compiler at least)
Automatic type promotion is happening and that is the result.
Here is some resource to learn.
http://docs.oracle.com/javase/specs/jls/se5.0/html/conversions.html
The next step would be is to learn to use formatters to format it to the given precision / requirements.
In my JAVA program there is code like this:
int f_part = (int) ((f_num - num) * 100);
f_num is double and num is long. I just want to take the fractional part out and assign it to f_part. But some times f_part value is one less than it's value. Which means if f_num = 123.55 and num = 123, But f_part equals to 54. And it happens only f_num and num is greater than 100. I don't know why this happening. Please can someone explain why this happens and way to correct it.
This is due to the limited precision in doubles.
The root of your problem is that the literal 123.55 actually represents the value 123.54999....
It may seem like it holds the value 123.55 if you print it:
System.out.println(123.55); // prints 123.55
but in fact, the printed value is an approximation. This can be revealed by creating a BigDecimal out of it, (which provides arbitrary precision) and print the BigDecimal:
System.out.println(new BigDecimal(123.55)); // prints 123.54999999999999715...
You can solve it by going via Math.round but you would have to know how many decimals the source double actually entails, or you could choose to go through the string representation of the double in fact goes through a fairly intricate algorithm.
If you're working with currencies, I strongly suggest you either
Let prices etc be represented by BigDecimal which allows you to store numbers as 0.1 accurately, or
Let an int store the number of cents (as opposed to having a double store the number of dollars).
Both ways are perfectly acceptable and used in practice.
From The Floating-Point Guide:
internally, computers use a format (binary floating-point) that cannot
accurately represent a number like 0.1, 0.2 or 0.3 at all.
When the code is compiled or interpreted, your “0.1” is already
rounded to the nearest number in that format, which results in a small
rounding error even before the calculation happens.
It looks like you're calculating money values. double is a completely inappropriate format for this. Use BigDecimal instead.
int f_part = (int) Math.round(((f_num - num) * 100));
This is one of the most often asked (and answered) questions. Floating point arithmetics can not produce exact results, because it's impossible to have an inifinity of real numbers inside 64 bits. Use BigDecimal if you need arbitrary precision.
Floating point arithmetic is not as simple as it may seem and there can be precision issues.
See Why can't decimal numbers be represented exactly in binary?, What Every Computer Scientist Should Know About Floating-Point Arithmetic for details.
If you need absolutely sure precision, you might want to use BigDecimal.