Win a copy of Microservices Testing (Live Project) this week in the Spring forum!
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
• Campbell Ritchie
• Tim Cooke
• Ron McLeod
• Jeanne Boyarsky
• Paul Clapham
Sheriffs:
• Liutauras Vilda
• Henry Wong
• Devaka Cooray
Saloon Keepers:
• Tim Moores
• Stephan van Hulst
• Tim Holloway
• Al Hobbs
• Carey Brown
Bartenders:
• Piet Souris
• Mikalai Zaikin
• Himai Minh

# fast method to round

Ranch Hand
Posts: 239
• Number of slices to send:
Optional 'thank-you' note:
I want to know if there's a fast and reliable method to perform rounding of a number for further calculations. For example, I need to convert length volume measurement unit.
It's a continuous process from one tank to the next tank, but each tank has different measurement.
Initially tank A is filled to the brim with liquid (1734 gallon). the liquids then transferred to tank B which measured with barrel, then to tank C (liter) and finally tank D (pint). Different measurement because the liquid is mixed with different chemicals in each tanks where each chemicals have different units. For now we'll just assume that the liquid will be transferred from tank A all the way to tank D without any change (the volume will always stay the same).
1 gallon = 0.0317 barrel
1 barrel = 119.2405 liter
1 liter = 2.1134 pint

so what I want is the volume report for each tank like this:
tank A = 1734 gallon
tank B = round( 1734 * 0.0317 , 2 ) = 54.9678 ~ 54.97 barrel
tank C = round( 54.97 * 119.2405, 2 ) = 6554.650285 ~ 6554.65 liter
tank D = round( 6554.65 * 2.1134, 2) = 13,852.59731 ~ 13,852.6 pint

the volume is first calculated and then rounded to two decimal places with the common rule (>=5 rounded up and <5 rounded down a.k.a truncated). the volume is calculated from previous tanks. thanks

Bartender
Posts: 10780
71
• Number of slices to send:
Optional 'thank-you' note:

Hendra Kurniawan wrote:I want to know if there's a fast and reliable method to perform rounding of a number for further calculations. For example, I need to convert length volume measurement unit.

Well, it really depends what you want:
1. If you're simply rounding for display, but are happy to retain extra precision internally; have a look at DecimalFormat or String.format(). Both will round a floating-point value for display.
2. If you need the the value itself to be rounded internally - particularly to a number of decimal places - you'd be better off using BigDecimal. Also, have a look at RoundingMode and MathContext.

Winston

Hendra Kurniawan
Ranch Hand
Posts: 239
• Number of slices to send:
Optional 'thank-you' note:
I tried BigDecimal, it's rather slow because of the string constructor, and then setscale and finally doublevalue. are the other methods reliable (reliable as in they'll produce correct rounded number for the next calculation) because as you can see in my first post I need to perform chained calculation using previous process' rounded calculation result for the next process' calculation. thanks

Winston Gutkowski
Bartender
Posts: 10780
71
• Number of slices to send:
Optional 'thank-you' note:

Hendra Kurniawan wrote:I tried BigDecimal, it's rather slow because of the string constructor, and then setscale and finally doublevalue.

I've never found it too bad. How fast do you need it to be? Also, you should use one of the constructors that takes a MathContext, rather than setting all that stuff yourself. [EDIT: beg pard; you do need to set the scale]

Also: why do you need doubleValue()? Just use BigDecimal's throughout.

Are the other methods reliable (reliable as in they'll produce correct rounded number for the next calculation) because as you can see in my first post I need to perform chained calculation using previous process' rounded calculation result for the next process' calculation. thanks

The class has been around for an awfully long time, so I suspect any problems would have been highlighted by now.

Winston

Hendra Kurniawan
Ranch Hand
Posts: 239
• Number of slices to send:
Optional 'thank-you' note:
is the constructor BigDecimal(long) precise? it's because the first number is guaranteed to be a round number (no decimal digits).

Hendra Kurniawan
Ranch Hand
Posts: 239
• Number of slices to send:
Optional 'thank-you' note:
also, won't mathcontext and setscale collide with each other? could you give me sample code? thanks

Winston Gutkowski
Bartender
Posts: 10780
71
• Number of slices to send:
Optional 'thank-you' note:

Hendra Kurniawan wrote:is the constructor BigDecimal(long) precise? it's because the first number is guaranteed to be a round number (no decimal digits).

Like I said: if it wasn't, you'd have heard about it long before now.

Winston

Winston Gutkowski
Bartender
Posts: 10780
71
• Number of slices to send:
Optional 'thank-you' note:

Hendra Kurniawan wrote:also, won't mathcontext and setscale collide with each other? could you give me sample code? thanks

No. MathContext has to do with precision and RoundingMode. Nothing to do with scale.

Winston

Hendra Kurniawan
Ranch Hand
Posts: 239
• Number of slices to send:
Optional 'thank-you' note:
ah yes. but which is best for rounding a calculation result?

BigDecimal a,b;

1. a.multiply(b,new mathcontext(precision, roundingmode))
2. a.multiply(b).setscale(precision, roundingmode);
3. a.multiply(b).round(new mathcontext(precision, rounding mode));

Winston Gutkowski
Bartender
Posts: 10780
71
• Number of slices to send:
Optional 'thank-you' note:

Hendra Kurniawan wrote:ah yes. but which is best for rounding a calculation result?

None of 'em. Have a look at the docs for BigDecimal.multiply().

[EDIT] God I talk rubbish sometimes. #2. Except it isn't 'precision', it's 'newScale'.

Winston

Hendra Kurniawan
Ranch Hand
Posts: 239
• Number of slices to send:
Optional 'thank-you' note:
none of them? does that mean they're just the same, or none of them works? as for method number 2, newScale is the same as precision, isn't it?

Rancher
Posts: 4250
57
• Number of slices to send:
Optional 'thank-you' note:
Hmmm, I would like to revisit Winston's first point:

Winston Gutkowski wrote:

Hendra Kurniawan wrote:I want to know if there's a fast and reliable method to perform rounding of a number for further calculations. For example, I need to convert length volume measurement unit.

Well, it really depends what you want:
1. If you're simply rounding for display, but are happy to retain extra precision internally; have a look at DecimalFormat or String.format(). Both will round a floating-point value for display.
2. If you need the the value itself to be rounded internally - particularly to a number of decimal places - you'd be better off using BigDecimal. Also, have a look at RoundingMode and MathContext.

Personally, I think #1 is much, much, much more appropriate here. What possible reason is there to round off the intermediate results, other than to mollify some end user who doesn't want to see scary decimal places? If you're representing physical quantities that have no natural reason to occur in integer values, why not just let them be? At least until you need to display something - but then, round only the display value. Don't round the actual numbers you're using to make further calculations. That just adds unnecessary errors to the subsequent results, in my opinion. I would keep the data types as double, all the way through, then use DecimalFormat or StringFormat to tidy up the displayed result.

Hendra Kurniawan
Ranch Hand
Posts: 239
• Number of slices to send:
Optional 'thank-you' note:
@Mike Simmons:
don't ask me why my client wants it that way. they just said that they want it that way, so I do as they say. the volumes in tank A to tank D will be displayed in the report, so they're all for display, not just volume in tank D.

Winston Gutkowski
Bartender
Posts: 10780
71
• 1
• Number of slices to send:
Optional 'thank-you' note:

Hendra Kurniawan wrote:don't ask me why my client wants it that way. they just said that they want it that way, so I do as they say. the volumes in tank A to tank D will be displayed in the report, so they're all for display, not just volume in tank D.

All the more reason to use option 1. Do your clients understand that rounding intermediate results is likely to lead to calculation errors? Furthermore they may be tricky to find and inconsistent. If it was me, and nobody was prepared to tell me why they wanted interims rounded, I'd probably ignore them and just display the results the way they want - but then, I'm an ornery old fart.

Winston

Mike Simmons
Rancher
Posts: 4250
57
• Number of slices to send:
Optional 'thank-you' note:
I agree. Here are the values I would get, using doubles all the way through:

tank a = 1734.0
tank b = a * 0.0317 = 54.9678
tank c = b * 119.2405 = 6554.3879559
tank d = c * 2.1134 = 13852.04350599906

To display these to the user, simply round each one:

tank a: 1734.00
tank b: 54.97
tank c: 6554.39
tank d: 13852.04

These are more accurate - or at least, more likely to be accurate - than the rounded values you showed originally. Your client probably won't even notice the difference. If they do, you can point out that this is better.

Of course, the 0.0317 only has three significant digits anyway, so in all subsequent calculations, anything more than three digits in the results is pretty much worthless. I don't know if it's worth pointing this out to your client or not.

Marshal
Posts: 76075
362
• Number of slices to send:
Optional 'thank-you' note:

Hendra Kurniawan wrote: . . . a round number (no decimal digits).

Do you mean “whole number” rather than “round number”, or does the phrase mean something different in India from Britain?

Hendra Kurniawan
Ranch Hand
Posts: 239
• Number of slices to send:
Optional 'thank-you' note:
oh yes, whole number. bad english, sometimes terminologies get mixed up. And yeah, I have explained the effect of my client's unusual request the difference between rounding first then calculate and calculate first and rounding for final result. apparently they still insist on rounding first then calculate. oh well, you're the boss since you have the money, so......
@Mike Simmons : you can see the obvious difference for volume of tank C and D right? that's my first application computation, which get rejected and must be replaced with the calculation shown in my first post.
and I'm not from india.

Campbell Ritchie
Marshal
Posts: 76075
362
• Number of slices to send:
Optional 'thank-you' note:
Sorry about thinking you are from India.

Mike Simmons
Rancher
Posts: 4250
57
• Number of slices to send:
Optional 'thank-you' note:

Hendra Kurniawan wrote:@Mike Simmons : you can see the obvious difference for volume of tank C and D right?

Yes, that was my point - mine were the corrected values.

Hendra Kurniawan wrote:that's my first application computation, which get rejected and must be replaced with the calculation shown in my first post.

OK, fair enough. It's tough working for foolish people.

So, why not just use some simple math?

or

Hendra Kurniawan
Ranch Hand
Posts: 239
• Number of slices to send:
Optional 'thank-you' note:

Mike Simmons wrote:
OK, fair enough. It's tough working for foolish people.

amen brother. but apparently in the real world, money makes you look smarter than you really are.

the math.round method, is it precise enough for my kind of calculation? I've read a lot of complaints regarding precision of double type.

Mike Simmons
Rancher
Posts: 4250
57
• Number of slices to send:
Optional 'thank-you' note:

Hendra Kurniawan wrote:the math.round method, is it precise enough for my kind of calculation? I've read a lot of complaints regarding precision of double type.

The double type is definitely precise enough, whether you use Math.round() or the first version of my code. They do the same thing.

Honestly, the type of rounding I suggest above will also work just fine for most of the applications where people recommend BigDecimal. You only really need BigDecimal if you need more than, ummm, 15-16 digits of accuracy I think. But you need to always remember to apply the rounding just before displaying any results, or people get upset by the extra .00000000001 or whatever they see after the expected end of the decimal. Using BigDecimal makes it easier to keep the required rounding without thinking about it - at the cost of a more cumbersome-looking API. (And a slower implementation, but usually that's an insignificant difference.)

Campbell Ritchie
Marshal
Posts: 76075
362
• Number of slices to send:
Optional 'thank-you' note:
I would say you should use BigDecimal even if you only want one place of precision. But I agree that using doubles makes sense in this instance because you can permit imprecision or inaccuracy. I would say use doubles anywhere where imprecision is not a problem, as would appear to be the case here. If you round to a small number of decimal places, you will rarely notice a difference between BigDecimal and double, but it is conceivable that you have a BigDecimal value 12.49999999999999 and a double value 12.50000000000001 after doing some arithmetic, in which case you will notice a difference when you round them.

Campbell Ritchie
Marshal
Posts: 76075
362
• Number of slices to send:
Optional 'thank-you' note:
Doesn’t the %f tag after printf or similar do the rounding for you? If you display both those numbers in my last post with %5.2f you will see 12.50, and if you use %2.0f, you will get 13 from one and 12 from the other? At least I think so; the details are probably in the java.util.Formatter class’ documentation.

Hendra Kurniawan
Ranch Hand
Posts: 239
• Number of slices to send:
Optional 'thank-you' note:
Ummm, in case you haven't noticed, precision is exactly the problem here. My client won't tolerate imprecision because those volumes will be further be multiplied with price per unit. So imprecision would mean ledger imbalance, or so they said. Some of the stuff have high price per unit, so even a slight difference in the last two decimal digits would produce undesired amount of money. So considering this case, can double still be used for my calculation? or should I stick with BigDecimal. And yes, BigDecimal is quite slow (I'm generating report for millions of rows, thus millions of rounding and multiplying). That's why I've searching for faster way to perform the rounding and calculation.

Winston Gutkowski
Bartender
Posts: 10780
71
• Number of slices to send:
Optional 'thank-you' note:

Campbell Ritchie wrote:Doesn’t the %f tag after printf or similar do the rounding for you? If you display both those numbers in my last post with %5.2f you will see 12.50, and if you use %2.0f, you will get 13 from one and 12 from the other?

Even better (probably): Use "%.2f" if you don't care how many digits are in front of the decimal point.

Winston

Winston Gutkowski
Bartender
Posts: 10780
71
• Number of slices to send:
Optional 'thank-you' note:

Hendra Kurniawan wrote:Ummm, in case you haven't noticed, precision is exactly the problem here. My client won't tolerate imprecision because those volumes will be further be multiplied with price per unit. So imprecision would mean ledger imbalance, or so they said.

You need to be a bit careful when you use terms like 'precision', because they mean different things to different people. In computing, precision refers to the total number of digits that a value can hold, regardless of decimal point. What you are referring to (rounding to a specific number of DP's) is called 'scale' or 'scaling'.

And while your client may think they mean precision, what they're actually talking about is mandated imprecision.

Winston

 PS: A little warning: This is where a lot of computerized hanky-panky such as 'penny-shaving' goes on, so you might want to be absolutely sure that your code can't be misused.

Bartender
Posts: 6109
6
• Number of slices to send:
Optional 'thank-you' note:

Hendra Kurniawan wrote:Ummm, in case you haven't noticed, precision is exactly the problem here.

Your next couple of sentences seem to say the exact opposite.

My client won't tolerate imprecision because those volumes will be further be multiplied with price per unit. So imprecision would mean ledger imbalance, or so they said. Some of the stuff have high price per unit, so even a slight difference in the last two decimal digits would produce undesired amount of money. So considering this case, can double still be used for my calculation? or should I stick with BigDecimal.

If you require a certain number of decimal digits of precision, then you have to go with BigD. For example, even the quantity 0.1 cannot be represented in a double.

However, if you require a particular relative precision, and that precision is within double's limits (about one part in 10^15 or 10^16, IIRC), and the compounded error from that precision will not throw the result of any calculation outside your required maximum error bounds, then you can use double.

I've only skimmed the thread so far, but if I understand correctly, you did one calculation with BigD or by hand, and came up with one particular value, and somebody else did the calculation with double, and, even though the double one was more accurate, you said your client requires the result to be as per your non-double version. Is that right? If so, then not only does your client require a minimum precision, he is also, in effect, requiring a maximum precision--that is, he's requiring a certain amount of error to enter into the calculations.

If that's indeed what it is, good luck sorting it out.

Hendra Kurniawan
Ranch Hand
Posts: 239
• Number of slices to send:
Optional 'thank-you' note:
@Winston:
okay, perhaps it's called scaling. the problem here is the rounding to two decimal digits of results' decimal places --> 0.125 becomes 0.13 and 0.124 becomes 0.12. I don't really understand accounting, so I don't really know why the client insist on this design. But he said that's the correct number demanded for government taxation purposes. That's the number that the government guys want for some quarterly report.

@Jeff:
nobody else is doing the calculation. I'm doing all the calculation. and to make the calculation consistent, I abstracted out the multiplication and rounding part to a separated method. So right now I only need to modify this one method to either use double or bigdecimal. the calculation result I need is the one stated in my first post. I don't know if double can achieve that or not. That's what I'm asking.

Mike Simmons
Rancher
Posts: 4250
57
• Number of slices to send:
Optional 'thank-you' note:
The method I presented will work fine most of the time - on rare occasions if might be "off" by 1 in the last digit. In those rare cases, neither my method nor the BigDecimal result is really more accurate (0.5 is equally distant from 0 or 1 after all) but government bean-counters may still want rounded it a certain way, consistently. Fine. (Though I would argue we really should find one of these boundary conditions in actual data, and see if the bean-counters are able to detect the difference, or explain why they think it should be a certain way.)

Ordinarily BigDecimal would be the standard way out of this. However if the speed of BigDecimal really is an issue (and have you tested it under realistic conditions to be sure?) then there are still other ways. Here's one:

This detects if the rounding is too close to do safely with double, and returns NaN if it's too close. (Ordinarily I'd prefer an exception, but I'm optimizing for speed here.) Now what you can do is calculate the results for each row using double, and check for NaN at the end (using Double.isNaN(), not using == Double.NaN). For any row that actually does have a NaN (and I would expect this to be extremely rare, maybe never) you can then repeat the calculation using BigDecimal, and use those results instead. This way you get the greater speed of double, and revert to BigDecimal only when it might make a difference. For testing purposes you can increase SAFETY_FACTOR as much as you like, to verify that you get the same results either way. But for running quickly you can keep it to something like 2-10 (never less than 1).

Mike Simmons
Rancher
Posts: 4250
57
• 1
• Number of slices to send:
Optional 'thank-you' note:
An alternate approach is to avoid both double and BigDecimal, and use longs for (almost) everything, doing all calculations in hundredths. Then show the results with the decimal moved back to the left. You have to be careful doing multiplication and division this way, as Java's integer arithmetic uses truncation rather than proper rounding. Here's a quick stab at it, minimally tested:

Winston Gutkowski
Bartender
Posts: 10780
71
• Number of slices to send:
Optional 'thank-you' note:

Hendra Kurniawan wrote:@Winston:
okay, perhaps it's called scaling. the problem here is the rounding to two decimal digits of results' decimal places --> 0.125 becomes 0.13 and 0.124 becomes 0.12.

Have a look at RoundingMode.HALF_UP, which can be used in calculations involving BigDecimal's (so possibly another reason to use them).

Winston

 Don't get me started about those stupid light bulbs.