round BigDecimal to nearest 5 cents

28,802

Solution 1

You can use plain double to do this.

double amount = 990.49;
double rounded = ((double) (long) (amount * 20 + 0.5)) / 20;

EDIT: for negative numbers you need to subtract 0.5

Solution 2

Using BigDecimal without any doubles (improved on the answer from marcolopes):

public static BigDecimal round(BigDecimal value, BigDecimal increment,
                               RoundingMode roundingMode) {
    if (increment.signum() == 0) {
        // 0 increment does not make much sense, but prevent division by 0
        return value;
    } else {
        BigDecimal divided = value.divide(increment, 0, roundingMode);
        BigDecimal result = divided.multiply(increment);
        return result;
    }
}

The rounding mode is e.g. RoundingMode.HALF_UP. For your examples, you actually want RoundingMode.UP (bd is a helper which just returns new BigDecimal(input)):

assertEquals(bd("1.05"), round(bd("1.03"), bd("0.05"), RoundingMode.UP));
assertEquals(bd("1.10"), round(bd("1.051"), bd("0.05"), RoundingMode.UP));
assertEquals(bd("1.05"), round(bd("1.05"), bd("0.05"), RoundingMode.UP));
assertEquals(bd("1.95"), round(bd("1.900001"), bd("0.05"), RoundingMode.UP));

Also note that there is a mistake in your last example (rounding 1.900001 to 1.10).

Solution 3

I'd try multiplying by 20, rounding to the nearest integer, then dividing by 20. It's a hack, but should get you the right answer.

Solution 4

I wrote this in Java a few years ago: https://github.com/marcolopes/dma/blob/master/org.dma.java/src/org/dma/java/math/BusinessRules.java

/**
 * Rounds the number to the nearest<br>
 * Numbers can be with or without decimals<br>
 */
public static BigDecimal round(BigDecimal value, BigDecimal rounding, RoundingMode roundingMode){

    return rounding.signum()==0 ? value :
        (value.divide(rounding,0,roundingMode)).multiply(rounding);

}


/**
 * Rounds the number to the nearest<br>
 * Numbers can be with or without decimals<br>
 * Example: 5, 10 = 10
 *<p>
 * HALF_UP<br>
 * Rounding mode to round towards "nearest neighbor" unless
 * both neighbors are equidistant, in which case round up.
 * Behaves as for RoundingMode.UP if the discarded fraction is >= 0.5;
 * otherwise, behaves as for RoundingMode.DOWN.
 * Note that this is the rounding mode commonly taught at school.
 */
public static BigDecimal roundUp(BigDecimal value, BigDecimal rounding){

    return round(value, rounding, RoundingMode.HALF_UP);

}


/**
 * Rounds the number to the nearest<br>
 * Numbers can be with or without decimals<br>
 * Example: 5, 10 = 0
 *<p>
 * HALF_DOWN<br>
 * Rounding mode to round towards "nearest neighbor" unless
 * both neighbors are equidistant, in which case round down.
 * Behaves as for RoundingMode.UP if the discarded fraction is > 0.5;
 * otherwise, behaves as for RoundingMode.DOWN.
 */
public static BigDecimal roundDown(BigDecimal value, BigDecimal rounding){

    return round(value, rounding, RoundingMode.HALF_DOWN);

}

Solution 5

Here are a couple of very simple methods in c# I wrote to always round up or down to any value passed.

public static Double RoundUpToNearest(Double passednumber, Double roundto)
    {

        // 105.5 up to nearest 1 = 106
        // 105.5 up to nearest 10 = 110
        // 105.5 up to nearest 7 = 112
        // 105.5 up to nearest 100 = 200
        // 105.5 up to nearest 0.2 = 105.6
        // 105.5 up to nearest 0.3 = 105.6

        //if no rounto then just pass original number back
        if (roundto == 0)
        {
            return passednumber;
        }
        else
        {
            return Math.Ceiling(passednumber / roundto) * roundto;
        }
    }
    public static Double RoundDownToNearest(Double passednumber, Double roundto)
    {

        // 105.5 down to nearest 1 = 105
        // 105.5 down to nearest 10 = 100
        // 105.5 down to nearest 7 = 105
        // 105.5 down to nearest 100 = 100
        // 105.5 down to nearest 0.2 = 105.4
        // 105.5 down to nearest 0.3 = 105.3

        //if no rounto then just pass original number back
        if (roundto == 0)
        {
            return passednumber;
        }
        else
        {
            return Math.Floor(passednumber / roundto) * roundto;
        }
    }
Share:
28,802
Dónal
Author by

Dónal

I earn a living by editing text files. I can be contacted at: [email protected] You can find out about all the different kinds of text files I've edited at: My StackOverflow Careers profile

Updated on September 05, 2022

Comments

  • Dónal
    Dónal over 1 year

    I'm trying to figure out how to round a monetary amount upwards to the nearest 5 cents. The following shows my expected results

    1.03     => 1.05
    1.051    => 1.10
    1.05     => 1.05
    1.900001 => 1.10
    

    I need the result to be have a precision of 2 (as shown above).

    Update

    Following the advice below, the best I could do is this

        BigDecimal amount = new BigDecimal(990.49)
    
        // To round to the nearest .05, multiply by 20, round to the nearest integer, then divide by 20
       def result =  new BigDecimal(Math.ceil(amount.doubleValue() * 20) / 20)
       result.setScale(2, RoundingMode.HALF_UP)
    

    I'm not convinced this is 100% kosher - I'm concerned precision could be lost when converting to and from doubles. However, it's the best I've come up with so far and seems to work.

  • Dónal
    Dónal over 14 years
    This throws an exception Exception thrown: Rounding necessary java.lang.ArithmeticException: Rounding necessary
  • robinst
    robinst almost 11 years
    I think using rounding.signum() == 0 would be a better test for 0 instead of rounding.doubleValue() == 0. Apart from that, this solution is good.
  • daemonl
    daemonl almost 11 years
    docs.oracle.com/javase/tutorial/java/nutsandbolts/… "As mentioned above, this data type [double] should never be used for precise values, such as currency."
  • Vishy
    Vishy almost 11 years
    @daemonl A good quote, except that most trading systems use double or long, especially those using C++.
  • Rajanik Jarasania
    Rajanik Jarasania almost 10 years
    Clearly the best answer. Please upvote so it's ranked higher than those hacky solutions that use floating point.
  • D. Kovács
    D. Kovács about 7 years
    @PeterLawrey source for this statement? I have never ever seen such a system, but I've worked with PL/I, C, and C++ systems in banks and other institutions...
  • Vishy
    Vishy about 7 years
    @D.Kovács which data type did they use? I have consulted for more than half the big banks in London and New York on these systems.
  • D. Kovács
    D. Kovács about 7 years
    @PeterLawrey The systems I have encountered always used self-built data structures for storing monetary data. I don't say this as an absolute, just stating that using plain double is a very, very, VERY bad idea.
  • Vishy
    Vishy about 7 years
    @D.Kovács I can understand developers not being comfortable with using double and it has it's quirks but in investment banks, they rarely use anything else.
  • Vishy
    Vishy about 7 years
    @DavidEasley I can understand developers not feeling comfortable using language primitives and operations like double, but that doesn't make using them hacky.
  • D. Kovács
    D. Kovács about 7 years
    @PeterLawrey interesting, I currently work in the insurance sector with Java, and everything is in BigDecimal...
  • Vishy
    Vishy about 7 years
    @D.Kovács I have worked on systems which are all BigDecimal. My first year of consulting was basically paid for by replacing BigDecimal with double for various clients, so I owe a lot to it ;)
  • Rajanik Jarasania
    Rajanik Jarasania about 7 years
    @PeterLawrey Genuinely interested to know how you did this without introducing rounding errors. Why are Oracle wrong to say double should never be used for precise values, such as currency?
  • Vishy
    Vishy about 7 years
    @DavidEasley there is a myth that you don't need to worry about rounding. It is rare that your operations are always so simple you never need to worry about rounding. Even if you use BigDecimal you need to get rounding right. The "nice" thing about double is it us often obviously wrong e.g 0.9999999999999998 but BigDecimal might be 0.99. Is it right or wrong?
  • Vishy
    Vishy about 7 years
    @DavidEasley in the case above, when you have two values which are represented without error and you perform an operation you get the nearest representable value to the result. As long as you are not working to the limits of the precision of double this isn't a problem.
  • Rajanik Jarasania
    Rajanik Jarasania about 7 years
    @PeterLawrey Thanks for responding but you're still giving us rather vague, handwavy answers. There are many references on the net to the danger of using floating point for things like currency, i.e. where precision is critical. Please can you point to just one reference (ideally a reputable one) that backs up your assertion that floating point (or at least double) is fine for financial calculations.
  • Vishy
    Vishy about 7 years
    @DavidEasley I would point to the IEEE-754 spec which explains exactly how it works in a consistent and predictable manner. What you consider reputable is purely a matter of opinion, I can tell you that the reality is that most investment banks use double in Java, and you can chose not to believe me if you like. Projects using BigDecimal have made me a lot of consulting money over the years, so please keep using it if you prefer it.
  • D. Kovács
    D. Kovács about 7 years
    @PeterLawrey, so that's why invesment banking has more problem than it should.
  • Vishy
    Vishy about 7 years
    @D.Kovács when a bank loses $84 billion in a quarter it wasn't a rounding error :P I was at bank which did this.
  • D. Kovács
    D. Kovács almost 7 years
    @PeterLawrey "I was" - and we know why the past tense ;) Joking apart: no that was a very elaborate hack. If we are talking about the same event. But in the case I have in my mind, it wasn't one bank but several. So maybe it's another :)
  • Tuupertunut
    Tuupertunut over 6 years
    They are not literally hacky but they only work on BigDecimals small enough to fit into double. Therefore they are not reliable solutions. This one works on any BigDecimal.
  • marcolopes
    marcolopes over 6 years
    Thanks! Thats correct... this code is old and i was new to java! :D signum() is the way to go!
  • user207421
    user207421 over 6 years
    Not a hack at all, a perfectly sound technique.
  • AbuNassar
    AbuNassar over 6 years
    Using BigDecimal is much more expressive of the fact that you're manipulating currency amounts.
  • Vishal Patel
    Vishal Patel over 3 years
    this one is the perfect answer and solves my problem.
  • Admin
    Admin over 3 years
    This pseudo takes inputs from line command where you can enter the value to round up along with round up value i.e. Enter the currency : $ 1.051 Enter the round up factor: $ 0.05 Final price after rounding up to 0.05 is : $1.10