简体   繁体   中英

Rounding a Java BigDecimal to the nearest interval

I have a BigDecimal calculation result which I need to round to the nearest specified interval (in this case it's the financial market tick size).

eg Price [Tick Size] -> Rounded Price

100.1 [0.25] -> 100
100.2 [0.25] -> 100.25
100.1 [0.125] -> 100.125
100.2 [0.125] -> 100.25

Thanks.

Update: schnaader's solution, translated into Java/BigDecimal terms:

price = price.divide(tick).setScale(0, RoundingMode.HALF_UP).multiply(tick)

You could normalize tick size and then use the usual rounding methods:

100.1 [0.25] -> * (1/0.25) -> 400.4 [1]  -> round -> 400 -> / (1/0.25) -> 100
100.2 [0.25] -> * (1/0.25) -> 400.8 [1] -> round -> 401 -> / (1/0.25) -> 100.25

So it should be:

Price = Round(Price / Tick) * Tick;

Also note that you seem to have to set the correct rounding mode for BigDecimals. See BigDecimal Docs for example. So you should be sure to set this correct and write some tests to check the correctness of your code.

This is more related to "Rounding a stock price to it's nearest tick size" .

Answer provided by schnaader is correct, however there are a few things missing.

  • First, you must check whether stock price needs rounding. Unnecessary rounding messes up the fractional part.
  • Then you also need to handle ArithmeticException that might come during BigDecimal divide function

Here's my solution. It would take a lot of time to explain it. I'd recommend to try it with some samples to get a feel. Look for the function roundTick() .

import static java.math.RoundingMode.HALF_UP;

import java.math.BigDecimal;

/**
 * Utility class for stock price related operations.
 */
public final class PriceFormatter {

    public static final float DELTA = 0.0001f;

    private PriceFormatter() {
    }

    /**
     * Rounds the price to the nearest tick size.
     *
     * @param price    price
     * @param tickSize tick size
     * @return price rounded to the nearest tick size
     */
    public static final float roundTick(final float price, final float tickSize) {
        if (tickSize < DELTA) {
            return price;
        }

        if (!isRoundingNeeded(price, tickSize)) {
            return price;
        }

        final BigDecimal p = new BigDecimal(price);
        final BigDecimal t = new BigDecimal(tickSize);

        final BigDecimal roundedPrice = p.divide(t, 0, HALF_UP).multiply(t);

        return roundedPrice.floatValue();
    }

    /**
     * Checks whether price needs rounding to the nearest tick size.
     *
     * @param price    price
     * @param tickSize tick size
     * @return true, if rounding is needed; false otherwise
     */
    public static final boolean isRoundingNeeded(final float price, final float tickSize) {
        final int mult = calculateTickMultiplier(tickSize);
        final int mod = (int) (tickSize * mult);
        final float reminder = (((price * mult) % mult) % mod);
        final boolean needsRounding = reminder > DELTA;
        return needsRounding;
    }

    public static final int calculateTickMultiplier(final float tickSize) {
        int result = 1;
        while (((tickSize * result) < 1) || (((tickSize * result) - (int) (tickSize * result)) > DELTA)) {
            result *= 10;
        }

        return result;
    }

}
p= p - p % t + ((p % t < t / 2) ? 0.0 : t);

// 其中 p= 价格和 t= 刻度增量

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM