4
\$\begingroup\$

The function I need:

Rounds a double to the specified number of decimal places

I figured this would be a part of java.lang.math, but inexplicably it doesn't have a round function that returns a double. Thus, my proposed solution is to add my own round function in a MathEx class.

/**
 * Holds generic math functions to extend the capabilities of the java.lang.Math class
 */
public class MathEx {

    /**
     * Rounds a double to the specified number of decimal places using the ubiquitous "half up" rounding method
     * @param numberToRound The number to be rounded
     * @param numberOfDecimals The number of decimals to include in the rounded number
     * @return The rounded number
     */
    public static double round(double numberToRound, int numberOfDecimals) {

        return new BigDecimal(numberToRound)
                    .setScale(numberOfDecimals, BigDecimal.ROUND_HALF_UP)
                    .doubleValue();
    }
}

I'd appreciate feedback on the overall strategy, the Javadoc comments, and the naming conventions.

\$\endgroup\$
  • \$\begingroup\$ The problem is interesting, but suggesting it to be solved through BigDecimal... I don't expect that to be a choice that the Java developers will agree with. \$\endgroup\$ – Simon Forsberg Apr 7 '15 at 23:53
  • \$\begingroup\$ @SimonAndréForsberg, interesting that you say that; rofl was just suggesting that I "keep everything as a BigDecimal number" \$\endgroup\$ – Elephants on Parade Apr 8 '15 at 14:25
  • \$\begingroup\$ If you need exact precision, then using a BigDecimal is pretty much the only option you have. If you don't need the exact precision, a version of Manny Meng's answer should suit you. \$\endgroup\$ – Simon Forsberg Apr 8 '15 at 14:36
2
\$\begingroup\$

This question makes assumptions which are not supported by the actual implementations of double in Java (or most other languages).

double is a floating-point number format, which has a number of odd behaviours that simply do not support 'rounding' in a consistently accurate way.

Consider the following code:

    double d = (v * 10 + 4) / 10_000_000.0;
    double rescale = round(d, 6) * 1_000_000_000.0;

That code takes an input value v, multiplies it by 10, adds 4, and then divides it by 10 million.

Consider an input value 65. Take that, multiply by 10, and add 4, to get 654. Now, divide 654 by 10 million, to get 0.0000654.

Now, round that value to 6 decimals, and get: 0.0000650, right?

Then multiply that by 1 billion to get 65000

Unfortunately, the actual result is:

64999.999999999990000000

The reason is that 0.000065 does not exist. Sure, many things that print numbers in Java will print a rounded value for you, but any math you do will have small errors inconsistent with your expectations.

The bottom line?

Rounding floating-point numbers to any fractional value will result in occasional errors.

The solution is to keep everything as a BigDecimal number. Do not use your method unless you know what you are sacrificing.

Here is an ideone, it loops through small values until it finds one which causes errors. The first error it finds is at 65 millionths.

\$\endgroup\$
  • \$\begingroup\$ That's interesting. This is for an application with 100k+ lines of code, so I can't change what's there. However, would you suggest that my future Java code should "only use primitive doubles when performance is a concern"? Class members, function arguments... all BigDecimal? Or would you agree with the sentiment in this answer and this C# answer which is "only use BigDecimal (or C#'s 'decimal') when you need an exact representation"? \$\endgroup\$ – Elephants on Parade Apr 8 '15 at 14:36
  • \$\begingroup\$ The standard Java response is this article: What every programmer should know about floats. If procision is important, then use BigDecimals, or know your error sources. \$\endgroup\$ – rolfl Apr 8 '15 at 14:39
1
\$\begingroup\$

Creating a new BigDecimal object is a bit expensive. Why don't you do:

public static double round(double numberToRound, int numberOfDecimals) {
    int tenPow = Math.pow(10, numberOfDecimals);
    return Math.round(numberToRound * tenPow) / tenPow;
}

This method is self explanatory:

  1. It first gets \$10^n\$ where \$n\$ is the number of decimals
  2. Then multiplies the number to round with the previous result, and rounds it to the nearest integer
  3. Finally divide \$10^n\$ from the result

This way, you don't have to create a BigDecimal.

You can further increase efficiency by making your own pow() method that only accepts a double and an int:

public static double pow(double x, int toPow) {
    if (toPow == 1) {
        return x;
    } else if (toPow == 0) {
        return 1;
    } else if (toPow < 0) {
        return pow(x, toPow + 1) / x;
    } else {
        return pow(x, toPow - 1) * x;
    }
}

Since Math.pow() also has to deal with stuff like \$2^{1.5}\$ and has to use a more complex algorithm to figure out the result, so a simple recursive algorithm can easily be more efficient than Math.pow().

\$\endgroup\$
  • \$\begingroup\$ How confident are you that this is better in any way? You assume that creating a BigDecimal is necessarily more expensive than what you are doing here. IMO, this code needs to be much more efficient before it's worth sacrificing the readability of the OP's original code. \$\endgroup\$ – Duncan Jones Mar 31 '15 at 8:20
  • \$\begingroup\$ @Duncan Not really far fetched that it is, without even looking at either code base. BigDecimal is an object, where Manny's answer deals with primitives. \$\endgroup\$ – Legato Apr 2 '15 at 0:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.