将 Java BigDecimal 舍入到最接近的间隔

2022-09-02 22:04:57

我有一个BigDecimal计算结果,我需要将其四舍五入到最接近的指定区间(在这种情况下,它是金融市场价格变动大小)。

例如:价格 [价格变动大小] -> 四舍五入价格

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

谢谢。

更新:schnaader的解决方案,翻译成Java/BigDecimal术语:

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

答案 1

您可以规范化价格变动大小,然后使用通常的舍入方法:

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

所以它应该是:

Price = Round(Price / Tick) * Tick;

另请注意,您似乎必须为大数位数设置正确的舍入模式。例如,请参阅BigDecimal Docs。因此,您应该确保正确设置此代码并编写一些测试来检查代码的正确性。


答案 2

这与“将股票价格四舍五入到最接近的刻度大小”更相关。

Schnaader提供的答案是正确的,但是缺少一些东西。

  • 首先,您必须检查股票价格是否需要四舍五入。不必要的四舍五入会弄乱分数部分。
  • 然后,您还需要处理在除法功能期间可能出现的ArithmeticExceptionBigDecimal

这是我的解决方案。这需要花费很多时间来解释它。我建议用一些样品来尝试一下,以获得一种感觉。查找函数 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;
    }

}