java 使用BigDecimal将小数舍入到5的倍数

6l7fqoea  于 2023-06-04  发布在  Java
关注(0)|答案(4)|浏览(261)

我想根据以下规则对BigDecimal小数部分进行舍入:

  • 1710.10变为1710.10
  • 1710.11变为1710.15
  • 1710.15变为1710.15
  • 1710.16变为1710.20

我尝试了这种方法new BigDecimal("1710.11").setScale(2, RoundingMode.HALF_UP)),期望得到1710.15,但我得到了1710.11。我也尝试了Apache Math Utils和Decimal Format,但没有办法实现这一点。

col17t5w

col17t5w1#

你的误会

首先,如果当前BigDecimal的小数位数比您指定的要高,那么在BigDecimal上设置小数位数只会返回一个具有不同值的BigDecimal。
比如说,

new BigDecimal("1710.113").setScale(2, RoundingMode.HALF_UP)

返回一个新的BigDecimal,等于new BigDecimal("1710.11")
但是,如果您在本例中的小数位数为2或更低,则新的BigDecimal保持不变(请阅读:相等)。
HALF_UP简单地表示BigDecimal以5结尾将导致更高的值,例如

new BigDecimal("1710.115").setScale(2, RoundingMode.HALF_UP)

返回等于new BigDecimal("1710.12")的结果

现在你的问题

由于你的“舍入”方式实际上并没有改变比例,我怀疑是否有一个已经存在的函数可以使用。然而,用手做其实很简单:

public class QuickMathz {

    public static void main(String[] args) {
        System.out.println(roundToNext5(new BigDecimal("1710.10"), 2));
        System.out.println(roundToNext5(new BigDecimal("1710.11"), 2));
        System.out.println(roundToNext5(new BigDecimal("1710.15"), 2));
        System.out.println(roundToNext5(new BigDecimal("1710.16"), 2));
        System.out.println(roundToNext5(new BigDecimal("1710.1135"), 2));
        System.out.println(roundToNext5(new BigDecimal("1710.1635"), 2));
        System.out.println(roundToNext5(new BigDecimal("1710.1675"), 2));
        System.out.println();
        System.out.println(roundToNext5(new BigDecimal("1710.10"), 3));
        System.out.println(roundToNext5(new BigDecimal("1710.11"), 3));
        System.out.println(roundToNext5(new BigDecimal("1710.15"), 3));
        System.out.println(roundToNext5(new BigDecimal("1710.16"), 3));
        System.out.println(roundToNext5(new BigDecimal("1710.1135"), 3));
        System.out.println(roundToNext5(new BigDecimal("1710.1635"), 3));
        System.out.println(roundToNext5(new BigDecimal("1710.1675"), 3));
    }

    public static BigDecimal roundToNext5(BigDecimal bigDecimal, int scale) {
        // Get the last digit we need to decide if we have to round to 0, 5 or 10
        int lastDigit = bigDecimal
                .movePointRight(scale)
                .remainder(BigDecimal.TEN).intValue();

        // Setting the Scale to scale - 1 to remove one more digit than we need
        // and then increase the scale to what we want
        BigDecimal result = bigDecimal
                .setScale(scale - 1, RoundingMode.DOWN)
                .setScale(scale, RoundingMode.UNNECESSARY);

        if (lastDigit == 0) {
            // Last digit is a 0 upscaling adds a 0
            return result;
        } else if (lastDigit <= 5) {
            // rounding up to 5
            return result.add(new BigDecimal("5").movePointLeft(scale));
        } else {
            // rounding up to 10
            return result.add(new BigDecimal("1").movePointLeft(scale - 1));
        }
    }
}

此类生成

1710.10
1710.15
1710.15
1710.20
1710.15
1710.20
1710.20

1710.100
1710.110
1710.150
1710.160
1710.115
1710.165
1710.170

(It既未优化也未检查负值,因此不要在关键环境中使用)

7jmck4yq

7jmck4yq2#

以下是我的解决方案:

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.math.BigDecimal;
import java.math.RoundingMode;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class CustomRoundingTests {

    @ParameterizedTest
    @CsvSource({
            "1710.10, 1710.10",
            "1710.11, 1710.15",
            "1710.15, 1710.15",
            "1710.16, 1710.20",
            "1710.96, 1711.00",

    })
    void testRoundToNearestFive(String input, String expected) {
        BigDecimalFiveRounded value = new BigDecimalFiveRounded(input);
        BigDecimal roundedValue = value.round();
        assertEquals(new BigDecimal(expected), roundedValue);
    }

    public static class BigDecimalFiveRounded extends BigDecimal {
        public BigDecimalFiveRounded(String val) {
            super(val);
        }

        public BigDecimal round() {
            BigDecimal fractionalPart = this.remainder(BigDecimal.ONE);
            BigDecimal scaledFractionalPart = fractionalPart.multiply(new BigDecimal("100"))
                    .setScale(0, RoundingMode.HALF_UP);
            int firstDigit = scaledFractionalPart.intValue() / 10;
            int secondDigit = scaledFractionalPart.intValue() % 10;

            var result = this.subtract(new BigDecimal("0." + firstDigit + secondDigit));
            if (secondDigit == 0) {
                // Nothing to do
                return this.setScale(2, RoundingMode.HALF_UP);
            } else if (secondDigit > 5) {
                firstDigit++;
                // Handle the case when we need to increase integer part
                if (firstDigit >= 10) {
                    firstDigit = 0;
                    result = result.add(BigDecimal.ONE);
                }
                secondDigit = 0;
            } else {
                secondDigit = 5;
            }

            var newFractionalPart = new BigDecimal("0." + firstDigit + secondDigit);
            return result.add(newFractionalPart).setScale(2, RoundingMode.HALF_UP);
        }
    }
}

测试结果:

[root]
testRoundToNearestFive(String, String)
[1] input=1710.10, expected=1710.10
[2] input=1710.11, expected=1710.15
[3] input=1710.15, expected=1710.15
[4] input=1710.16, expected=1710.20
[5] input=1710.96, expected=1711.00
cdmah0mi

cdmah0mi3#

我有时会使用一个Currency类和一个round方法:

public class Currency {
    private final String symbol;
    private final BigDecimal minDenomination;

    public Currency(String symbol, BigDecimal minDenomination) {
        this.symbol = symbol;
        this.minDenomination = minDenomination;
    }

    public String getSymbol() {
        return symbol;
    }

    public BigDecimal getMinDenomination() {
        return minDenomination;
    }

    public BigDecimal round(BigDecimal value, RoundingMode mode) {
        return value.divide(minDenomination)
                .setScale(0, mode)
                .multiply(minDenomination)
                .setScale(minDenomination.scale());
    }
}

然后:

String[] values = {
        "1710.10",
        "1710.11",
        "1710.15",
        "1710.16"        };
    Currency chf = new Currency("CHF", new BigDecimal("0.05"));
    for (String s: values) {
        BigDecimal val = new BigDecimal(s);
        System.out.println(val + " -> " + chf.round(val, RoundingMode.UP));
    }
    System.out.println();
    Currency usd = new Currency("USD", new BigDecimal("0.01"));
    for (String s: values) {
        BigDecimal val = new BigDecimal(s);
        System.out.println(val + " -> " + usd.round(val, RoundingMode.UP));
    }
uz75evzq

uz75evzq4#

大多数人都熟悉从以5结尾的分数向上舍入。这将是RoundingMode.HALF_UP,其中5或更多向上舍入到相邻数字,如果小于5则向下舍入。其他模式将在here中详细说明。做你想做的事需要一些定制。在这里,我使用一个lambda,它可以应用于scale valueBigDecimal作为参数。它提取整数部分和小数部分,并根据要求对小数部分进行数学调整,返回一个四舍五入的BigDecimal。

BiFunction<Integer, BigDecimal, BigDecimal> round = (scale, bigD) -> {
     bigD = bigD.movePointRight(scale-2);
     String bigDStr = bigD.toString();
     int i = bigDStr.indexOf(".");
     String save = bigDStr.substring(0,i);
     int fract = Integer.parseInt(bigDStr.substring(i+1));
     fract = (fract+4)/5*5;
     StringBuilder sb = new StringBuilder(save).append(".").append(fract);
     return new BigDecimal(sb.toString()).movePointLeft(scale-2); 
 };

String[] values = {
        "1710.10","1710.11","1710.15","1710.16","1710.10","1710.11",
        "1710.15","1710.16"
};

for (String val : values) {
    BigDecimal d = new BigDecimal(val);
    System.out.printf("%.2f  ->  %.2f%n", d, round.apply(2, d));
}

印刷品

1710.10  ->  1710.10
1710.11  ->  1710.15
1710.15  ->  1710.15
1710.16  ->  1710.20
1710.10  ->  1710.10
1710.11  ->  1710.15
1710.15  ->  1710.15
1710.16  ->  1710.20

还有几个例子

System.out.println(round.apply(3,new BigDecimal("1710.124")));
System.out.println(round.apply(4,new BigDecimal("1710.1247")));
System.out.println(round.apply(5,new BigDecimal("1710.12489")));
System.out.println(round.apply(6,new BigDecimal("1710.124542")));

印刷品

1710.125
1710.1250
1710.12490
1710.124545

相关问题