Odd Rounding Logic - java

-- Short Solution --
Thank you for all the informative replies... #PetrJaneček #Matt pointed me in the right direction. I agree I had to brush up on my understanding of floating point arithmetic; but the difference in calling the constructor vs double's canonical string representation by using valueOf solved my problem.
private static Double sampleRound(Double value, int places) {
BigDecimal bd1 = BigDecimal.valueOf(value);
bd1 = bd1.setScale(places, RoundingMode.HALF_UP);
return bd1.doubleValue();
}
As provided by #PetrJaneček a useful video:
youtu.be/wbp-3BJWsU8?t=246
-- Original Question --
I've come across a really odd one regarding rounding and just want to understand where I'm going wrong.
The function is:
private static Double sampleRound(Double value, int places) {
BigDecimal bd1 = new BigDecimal(value);
bd1 = bd1.setScale(places, RoundingMode.HALF_UP);
return bd1.doubleValue();
}
If I pass the value "1942.945", I would expect it to output "1942.95", but instead I get "1942.94". However if I pass "1942.9451" I get "1942.95". So ok fair then let's assume I carry over this logic to 3 decimal places instead. So if I pass "1942.9445" I expect "1942.944", but instead I get "1942.945", how and why? The logic seems broken to me?
sampleRound(1942.945, 2) -> 1942.94
[Does NOT make sense, should be 1942.95]
sampleRound(1942.9450000000001, 2)-> 1942.95
*Edit: I've also passed this (1 additional zero), again I understand data types and it's constraints, I guess the point I'm making is the fraction .005 isn't being seen as a half up >= 5: in accordance with the documentation of java "discarded fraction is ≥ 0.5;" it's essentially seeing it as > 0.5 not >= 0.5 *
sampleRound(1942.94500000000001, 2) -> 1942.94
[Then this should be equivalent to the top]
Ok but then fine I can deal with the above logic, but as soon as I do 3 decimal places:
sampleRound(1942.9445, 3) -> 1942.945
[Does NOT make sense, in accordance with top logic should be 19423.944].
Then just like I mentioned in the above logic where franaction .005 isn't being seen as a >= 0.5 then .0005 should be treated the same way?
I hope by description makes sense, but I'm stumped?
Regards,

As suggested in a comment by #PetrJaneček You can get around this issue by using BigDecimal.valueOf that will create a BigDecimal "using the double's canonical string representation provided by the Double.toString(double) method.".
The BigDecimal constructor, new BigDecimal(double) creates a BigDecimal based on the exact value of the the double provided.
double v = 1942.945;
System.out.println( Double.toString(v) );
System.out.println( new BigDecimal(v) );
System.out.println( BigDecimal.valueOf(v) );
1942.945
1942.944999999999936335370875895023345947265625
1942.945
What we see, is first the "canonical string" of v which is what you expect, but the exact value is not "1942.945" it is slightly less. We can see that when we use the BigDecimal constructor and get the exact value back.
For the rest of your examples, we just have to look at the exact representation.
System.out.println( new BigDecimal( 1942.9450000000001 ) );
1942.94500000000016370904631912708282470703125
System.out.println( new BigDecimal( 1942.94500000000001 ) );
1942.944999999999936335370875895023345947265625
System.out.println( new BigDecimal( 1942.9445 ) );
1942.94450000000006184563972055912017822265625
From that, I think it is clear why your rounding behavior is the way it is.

String v = "1942.945";
System.out.println(new BigDecimal(v).toPlainString());
As double has no precision, scale, 1942.945 is just an approximation and might be nearer to 1942.9449999983; in fact those two doubles might be identical. Then setting the scale starts off with an imprecise approximation.
So use a String from which BigDecimal immediately might deduce its precision/scale.

Related

Big Error in Java or incongruency with simple operators

I'm beginning in Java, and could anyone explain me why Java gives me these answers?
I have a very simple class trying to learn how to round a number.
I want 2 decimals
so...
public static void main(String[] args) {
double pi1 = Math.PI;
System.out.println("pi1 = " + pi1);
double p2 ;
p2= Math.round(pi1*100)/100;
//p2= Math.round(pi1*100)
//p2=p2/100;
System.out.println("p2 = " + p2);
}
If I run this result is:
p2 = 3.0
Then I change
//p2= Math.round(pi1*100)/100;
p2 = Math.round(pi1*100);
p2 = p2/100;
Now, result is:
p2 = 3.14
as I wanted
Why with these differences? Why the first option doesn't give me 3.14
I think that I've made a correct code with 1st option.
Please, anyone could tell me why?
These things makes me don't trust Java.
Thank you.
I will assume that you know how integer division works in Java. In short, when both sides of / are integral types, like in 314 / 100, the expression evaluates to an integer too, like 3.
Math.round returns a long, which is an integral type. In your first code, you have the expression Math.round(pi1*100)/100. Math.round(...) returns an integer type, 100 is an integer literal, so integer division occurs.
However, in the second code, you first assigned the result of Math.round to p2. The long returned is implicitly converted to a double first, and stored in p2. You then wrote an expression in p2: p2/100. Here, one of the operands is double, so integer division does not occur.
Therefore, the one liner version that is the same as the second code is:
p2 = ((double)Math.round(pi1*100))/100;
You don't see the double conversion in the second code because it is done implicitly.
A note on rounding
This way rounding doubles should be used if you want to do calculations with the rounded number afterwards. If you just want to display a rounded number as output, you should use String.format, System.out.printf, or DecimalFormat. Read more about these methods here.
No mistake here, it works properly. Method Math.round(double) returns type long.
In your first variant, you receive result long 314 and divide it by 100 and get the result 3, and then assign it back to double.
In your second variant, you receive long result 314 and assign it back to double. Dividing double keeps precision as opposed to dividing long, so you get the correct result of 3.14
There for make p2= Math.round(pi1*100)/100; as
p2= Math.round(pi1*100) / 100.0;
Output -:
pi1 = 3.141592653589793
p2 = 3.14

Best method to round a double in Java 12/13? [duplicate]

This question already has answers here:
How to round a number to n decimal places in Java
(39 answers)
Closed 8 years ago.
If the value is 200.3456, it should be formatted to 200.34.
If it is 200, then it should be 200.00.
Here's an utility that rounds (instead of truncating) a double to specified number of decimal places.
For example:
round(200.3456, 2); // returns 200.35
Original version; watch out with this
public static double round(double value, int places) {
if (places < 0) throw new IllegalArgumentException();
long factor = (long) Math.pow(10, places);
value = value * factor;
long tmp = Math.round(value);
return (double) tmp / factor;
}
This breaks down badly in corner cases with either a very high number of decimal places (e.g. round(1000.0d, 17)) or large integer part (e.g. round(90080070060.1d, 9)). Thanks to Sloin for pointing this out.
I've been using the above to round "not-too-big" doubles to 2 or 3 decimal places happily for years (for example to clean up time in seconds for logging purposes: 27.987654321987 -> 27.99). But I guess it's best to avoid it, since more reliable ways are readily available, with cleaner code too.
So, use this instead
(Adapted from this answer by Louis Wasserman and this one by Sean Owen.)
public static double round(double value, int places) {
if (places < 0) throw new IllegalArgumentException();
BigDecimal bd = BigDecimal.valueOf(value);
bd = bd.setScale(places, RoundingMode.HALF_UP);
return bd.doubleValue();
}
Note that HALF_UP is the rounding mode "commonly taught at school". Peruse the RoundingMode documentation, if you suspect you need something else such as Bankers’ Rounding.
Of course, if you prefer, you can inline the above into a one-liner:
new BigDecimal(value).setScale(places, RoundingMode.HALF_UP).doubleValue()
And in every case
Always remember that floating point representations using float and double are inexact.
For example, consider these expressions:
999199.1231231235 == 999199.1231231236 // true
1.03 - 0.41 // 0.6200000000000001
For exactness, you want to use BigDecimal. And while at it, use the constructor that takes a String, never the one taking double. For instance, try executing this:
System.out.println(new BigDecimal(1.03).subtract(new BigDecimal(0.41)));
System.out.println(new BigDecimal("1.03").subtract(new BigDecimal("0.41")));
Some excellent further reading on the topic:
Item 48: "Avoid float and double if exact answers are required" in Effective Java (2nd ed) by Joshua Bloch
What Every Programmer Should Know About Floating-Point Arithmetic
If you wanted String formatting instead of (or in addition to) strictly rounding numbers, see the other answers.
Specifically, note that round(200, 0) returns 200.0. If you want to output "200.00", you should first round and then format the result for output (which is perfectly explained in Jesper's answer).
If you just want to print a double with two digits after the decimal point, use something like this:
double value = 200.3456;
System.out.printf("Value: %.2f", value);
If you want to have the result in a String instead of being printed to the console, use String.format() with the same arguments:
String result = String.format("%.2f", value);
Or use class DecimalFormat:
DecimalFormat df = new DecimalFormat("####0.00");
System.out.println("Value: " + df.format(value));
I think this is easier:
double time = 200.3456;
DecimalFormat df = new DecimalFormat("#.##");
time = Double.valueOf(df.format(time));
System.out.println(time); // 200.35
Note that this will actually do the rounding for you, not just formatting.
The easiest way, would be to do a trick like this;
double val = ....;
val = val*100;
val = Math.round(val);
val = val /100;
if val starts at 200.3456 then it goes to 20034.56 then it gets rounded to 20035 then we divide it to get 200.34.
if you wanted to always round down we could always truncate by casting to an int:
double val = ....;
val = val*100;
val = (double)((int) val);
val = val /100;
This technique will work for most cases because for very large doubles (positive or negative) it may overflow. but if you know that your values will be in an appropriate range then this should work for you.
Please use Apache commons math:
Precision.round(10.4567, 2)
function Double round2(Double val) {
return new BigDecimal(val.toString()).setScale(2,RoundingMode.HALF_UP).doubleValue();
}
Note the toString()!!!!
This is because BigDecimal converts the exact binary form of the double!!!
These are the various suggested methods and their fail cases.
// Always Good!
new BigDecimal(val.toString()).setScale(2,RoundingMode.HALF_UP).doubleValue()
Double val = 260.775d; //EXPECTED 260.78
260.77 - WRONG - new BigDecimal(val).setScale(2,RoundingMode.HALF_UP).doubleValue()
Double val = 260.775d; //EXPECTED 260.78
260.77 - TRY AGAIN - Math.round(val * 100.d) / 100.0d
Double val = 256.025d; //EXPECTED 256.03d
256.02 - OOPS - new DecimalFormat("0.00").format(val)
// By default use half even, works if you change mode to half_up
Double val = 256.025d; //EXPECTED 256.03d
256.02 - FAIL - (int)(val * 100 + 0.5) / 100.0;
double value= 200.3456;
DecimalFormat df = new DecimalFormat("0.00");
System.out.println(df.format(value));
If you really want the same double, but rounded in the way you want you can use BigDecimal, for example
new BigDecimal(myValue).setScale(2, RoundingMode.HALF_UP).doubleValue();
double d = 28786.079999999998;
String str = String.format("%1.2f", d);
d = Double.valueOf(str);
For two rounding digits. Very simple and you are basically updating the variable instead of just display purposes which DecimalFormat does.
x = Math.floor(x * 100) / 100;
Rounding a double is usually not what one wants. Instead, use String.format() to represent it in the desired format.
In your question, it seems that you want to avoid rounding the numbers as well? I think .format() will round the numbers using half-up, afaik?
so if you want to round, 200.3456 should be 200.35 for a precision of 2. but in your case, if you just want the first 2 and then discard the rest?
You could multiply it by 100 and then cast to an int (or taking the floor of the number), before dividing by 100 again.
200.3456 * 100 = 20034.56;
(int) 20034.56 = 20034;
20034/100.0 = 200.34;
You might have issues with really really big numbers close to the boundary though. In which case converting to a string and substring'ing it would work just as easily.
value = (int)(value * 100 + 0.5) / 100.0;

ArithmeticException: "Non-terminating decimal expansion; no exact representable decimal result"

Why does the following code raise the exception shown below?
BigDecimal a = new BigDecimal("1.6");
BigDecimal b = new BigDecimal("9.2");
a.divide(b) // results in the following exception.
Exception:
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
From the Java 11 BigDecimal docs:
When a MathContext object is supplied with a precision setting of 0 (for example, MathContext.UNLIMITED), arithmetic operations are exact, as are the arithmetic methods which take no MathContext object. (This is the only behavior that was supported in releases prior to 5.)
As a corollary of computing the exact result, the rounding mode setting of a MathContext object with a precision setting of 0 is not used and thus irrelevant. In the case of divide, the exact quotient could have an infinitely long decimal expansion; for example, 1 divided by 3.
If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations.
To fix, you need to do something like this:
a.divide(b, 2, RoundingMode.HALF_UP)
where 2 is the scale and RoundingMode.HALF_UP is rounding mode
For more details see this blog post.
Because you're not specifying a precision and a rounding-mode. BigDecimal is complaining that it could use 10, 20, 5000, or infinity decimal places, and it still wouldn't be able to give you an exact representation of the number. So instead of giving you an incorrect BigDecimal, it just whinges at you.
However, if you supply a RoundingMode and a precision, then it will be able to convert (eg. 1.333333333-to-infinity to something like 1.3333 ... but you as the programmer need to tell it what precision you're 'happy with'.
You can do
a.divide(b, MathContext.DECIMAL128)
You can choose the number of bits you want: either 32, 64 or 128.
Check out this link :
http://edelstein.pebbles.cs.cmu.edu/jadeite/main.php?api=java6&state=class&package=java.math&class=MathContext
To fix such an issue I have used the below code
a.divide(b, 2, RoundingMode.HALF_EVEN)
Where 2 is scale. Now the problem should be resolved.
I had this same problem, because my line of code was:
txtTotalInvoice.setText(var1.divide(var2).doubleValue() + "");
I change to this, reading previous Answer, because I was not writing decimal precision:
txtTotalInvoice.setText(var1.divide(var2,4, RoundingMode.HALF_UP).doubleValue() + "");
4 is Decimal Precison
AND RoundingMode are Enum constants, you could choose any of this
UP, DOWN, CEILING, FLOOR, HALF_DOWN, HALF_EVEN, HALF_UP
In this Case HALF_UP, will have this result:
2.4 = 2
2.5 = 3
2.7 = 3
You can check the RoundingMode information here: http://www.javabeat.net/precise-rounding-of-decimals-using-rounding-mode-enumeration/
It´s a issue of rounding the result, the solution for me is the following.
divider.divide(dividend,RoundingMode.HALF_UP);
Answer for BigDecimal throws ArithmeticException
public static void main(String[] args) {
int age = 30;
BigDecimal retireMentFund = new BigDecimal("10000.00");
retireMentFund.setScale(2,BigDecimal.ROUND_HALF_UP);
BigDecimal yearsInRetirement = new BigDecimal("20.00");
String name = " Dennis";
for ( int i = age; i <=65; i++){
recalculate(retireMentFund,new BigDecimal("0.10"));
}
BigDecimal monthlyPension = retireMentFund.divide(
yearsInRetirement.divide(new BigDecimal("12"), new MathContext(2, RoundingMode.CEILING)), new MathContext(2, RoundingMode.CEILING));
System.out.println(name+ " will have £" + monthlyPension +" per month for retirement");
}
public static void recalculate (BigDecimal fundAmount, BigDecimal rate){
fundAmount.multiply(rate.add(new BigDecimal("1.00")));
}
Add MathContext object in your divide method call and adjust precision and rounding mode. This should fix your problem
Your program does not know what precision for decimal numbers to use so it throws:
java.lang.ArithmeticException: Non-terminating decimal expansion
Solution to bypass exception:
MathContext precision = new MathContext(int setPrecisionYouWant); // example 2
BigDecimal a = new BigDecimal("1.6",precision);
BigDecimal b = new BigDecimal("9.2",precision);
a.divide(b) // result = 0.17
For me, it's working with this:
BigDecimal a = new BigDecimal("9999999999.6666",precision);
BigDecimal b = new BigDecimal("21",precision);
a.divideToIntegralValue(b).setScale(2)

Round a double to 2 decimal places [duplicate]

This question already has answers here:
How to round a number to n decimal places in Java
(39 answers)
Closed 8 years ago.
If the value is 200.3456, it should be formatted to 200.34.
If it is 200, then it should be 200.00.
Here's an utility that rounds (instead of truncating) a double to specified number of decimal places.
For example:
round(200.3456, 2); // returns 200.35
Original version; watch out with this
public static double round(double value, int places) {
if (places < 0) throw new IllegalArgumentException();
long factor = (long) Math.pow(10, places);
value = value * factor;
long tmp = Math.round(value);
return (double) tmp / factor;
}
This breaks down badly in corner cases with either a very high number of decimal places (e.g. round(1000.0d, 17)) or large integer part (e.g. round(90080070060.1d, 9)). Thanks to Sloin for pointing this out.
I've been using the above to round "not-too-big" doubles to 2 or 3 decimal places happily for years (for example to clean up time in seconds for logging purposes: 27.987654321987 -> 27.99). But I guess it's best to avoid it, since more reliable ways are readily available, with cleaner code too.
So, use this instead
(Adapted from this answer by Louis Wasserman and this one by Sean Owen.)
public static double round(double value, int places) {
if (places < 0) throw new IllegalArgumentException();
BigDecimal bd = BigDecimal.valueOf(value);
bd = bd.setScale(places, RoundingMode.HALF_UP);
return bd.doubleValue();
}
Note that HALF_UP is the rounding mode "commonly taught at school". Peruse the RoundingMode documentation, if you suspect you need something else such as Bankers’ Rounding.
Of course, if you prefer, you can inline the above into a one-liner:
new BigDecimal(value).setScale(places, RoundingMode.HALF_UP).doubleValue()
And in every case
Always remember that floating point representations using float and double are inexact.
For example, consider these expressions:
999199.1231231235 == 999199.1231231236 // true
1.03 - 0.41 // 0.6200000000000001
For exactness, you want to use BigDecimal. And while at it, use the constructor that takes a String, never the one taking double. For instance, try executing this:
System.out.println(new BigDecimal(1.03).subtract(new BigDecimal(0.41)));
System.out.println(new BigDecimal("1.03").subtract(new BigDecimal("0.41")));
Some excellent further reading on the topic:
Item 48: "Avoid float and double if exact answers are required" in Effective Java (2nd ed) by Joshua Bloch
What Every Programmer Should Know About Floating-Point Arithmetic
If you wanted String formatting instead of (or in addition to) strictly rounding numbers, see the other answers.
Specifically, note that round(200, 0) returns 200.0. If you want to output "200.00", you should first round and then format the result for output (which is perfectly explained in Jesper's answer).
If you just want to print a double with two digits after the decimal point, use something like this:
double value = 200.3456;
System.out.printf("Value: %.2f", value);
If you want to have the result in a String instead of being printed to the console, use String.format() with the same arguments:
String result = String.format("%.2f", value);
Or use class DecimalFormat:
DecimalFormat df = new DecimalFormat("####0.00");
System.out.println("Value: " + df.format(value));
I think this is easier:
double time = 200.3456;
DecimalFormat df = new DecimalFormat("#.##");
time = Double.valueOf(df.format(time));
System.out.println(time); // 200.35
Note that this will actually do the rounding for you, not just formatting.
The easiest way, would be to do a trick like this;
double val = ....;
val = val*100;
val = Math.round(val);
val = val /100;
if val starts at 200.3456 then it goes to 20034.56 then it gets rounded to 20035 then we divide it to get 200.34.
if you wanted to always round down we could always truncate by casting to an int:
double val = ....;
val = val*100;
val = (double)((int) val);
val = val /100;
This technique will work for most cases because for very large doubles (positive or negative) it may overflow. but if you know that your values will be in an appropriate range then this should work for you.
Please use Apache commons math:
Precision.round(10.4567, 2)
function Double round2(Double val) {
return new BigDecimal(val.toString()).setScale(2,RoundingMode.HALF_UP).doubleValue();
}
Note the toString()!!!!
This is because BigDecimal converts the exact binary form of the double!!!
These are the various suggested methods and their fail cases.
// Always Good!
new BigDecimal(val.toString()).setScale(2,RoundingMode.HALF_UP).doubleValue()
Double val = 260.775d; //EXPECTED 260.78
260.77 - WRONG - new BigDecimal(val).setScale(2,RoundingMode.HALF_UP).doubleValue()
Double val = 260.775d; //EXPECTED 260.78
260.77 - TRY AGAIN - Math.round(val * 100.d) / 100.0d
Double val = 256.025d; //EXPECTED 256.03d
256.02 - OOPS - new DecimalFormat("0.00").format(val)
// By default use half even, works if you change mode to half_up
Double val = 256.025d; //EXPECTED 256.03d
256.02 - FAIL - (int)(val * 100 + 0.5) / 100.0;
double value= 200.3456;
DecimalFormat df = new DecimalFormat("0.00");
System.out.println(df.format(value));
If you really want the same double, but rounded in the way you want you can use BigDecimal, for example
new BigDecimal(myValue).setScale(2, RoundingMode.HALF_UP).doubleValue();
double d = 28786.079999999998;
String str = String.format("%1.2f", d);
d = Double.valueOf(str);
For two rounding digits. Very simple and you are basically updating the variable instead of just display purposes which DecimalFormat does.
x = Math.floor(x * 100) / 100;
Rounding a double is usually not what one wants. Instead, use String.format() to represent it in the desired format.
In your question, it seems that you want to avoid rounding the numbers as well? I think .format() will round the numbers using half-up, afaik?
so if you want to round, 200.3456 should be 200.35 for a precision of 2. but in your case, if you just want the first 2 and then discard the rest?
You could multiply it by 100 and then cast to an int (or taking the floor of the number), before dividing by 100 again.
200.3456 * 100 = 20034.56;
(int) 20034.56 = 20034;
20034/100.0 = 200.34;
You might have issues with really really big numbers close to the boundary though. In which case converting to a string and substring'ing it would work just as easily.
value = (int)(value * 100 + 0.5) / 100.0;

Java loss of precision

I have a problem concerned with losing of precision
my task is to print numbers as strings
int exponent = ...
int[] Mantissas = { 1, 2, 5 };
double dataStep = java.lang.Math.pow(10.0, exponent) * Mantissas[mantissaIndex];
...
for (int i = 0; i < NSteps; i++)
steps[i] = firstStep + i * dataStep;
draw(steps);
for example, 0.2*7=1.4000000000000001; 0.0000014/10=1.3999999999999998E-7
how to figure out this problem?
UPD: The main problem is string output formating. i don't bother about losting of about 0.00000001 value.
Now I solved it as String.format("%f", value),
but I think it's not good approach
As mentioned by others you have to use java.math.BigDecimal instead of float/double. This however comes with its own set of problems.
For instance when you call BigDecimal(double) the value you pass in will be expanded to its full representation:
BigDecimal oneTenth = new BigDecimal(0.1);
BigDecimal oneMillion = new BigDecimal(1000000);
oneTenth.multiply(oneMillion)
out> 100000.0000000000055511151231257827021181583404541015625000000
But when you use the BigDecimal(String) constructor the eact value is represented and you get
BigDecimal oneTenth = new BigDecimal("0.1");
BigDecimalr oneMillion = new BigDecimal(1000000);
oneTenth.multiply(oneMillion)
out> 100000.0
You can read more on BigDecimal's limitations in Joshua Bloch and Neal Gafter's splendid Java puzzlers book and in this informative article. Finally note that toString on BigDecimal's will print in scientific notation so you will properly have to use toPlainString instead.
The double type does not have infinite precision and cannot represent decimals exactly. You are observing normal rounding errors. For arbitrary precision arithmetic, you will need to use java.math.BigDecimal instead.
Search for "floating point numbers" on SO and you'll get a slew of answers as to why this happens. It has to do with how floating point numbers are represented in computers.
How is floating point stored? When does it matter?
Another article on the matter - Floating Point Approximation

Categories