1

我想将 BigDecimal 值转换为不超过一定数量字符的字符串,必要时切换到科学记数法。我怎样才能做到这一点?

换句话说,我如何编写formatDecimal(BigDecimal number, int maxChars)能够通过以下测试的函数:

    final int maxChars = 6;

    assertEquals(
            "0.3333",
            formatDecimal(new BigDecimal(0.3333333333333), maxChars)
            );

    assertEquals(
            "1.6E+6", 
            formatDecimal(new BigDecimal(1555555), maxChars)
            );

    assertEquals(
            "1.6E-5", 
            formatDecimal(new BigDecimal(0.0000155), maxChars)
            );

    assertEquals(
            "1234.6", 
            formatDecimal(new BigDecimal(1234.56789), maxChars)
            );

    assertEquals(
            "123", 
            formatDecimal(new BigDecimal(123), maxChars)
            );

    assertEquals(
            "0", 
            formatDecimal(new BigDecimal(0), maxChars)
            );

在我的应用程序中,maxChars实际上并不需要成为参数——它将在编译时固定(到 18),因此使用例如的解决方案DecimalFormat("#.####")可以工作,只要可以正确管理到科学记数法的切换。

4

3 回答 3

1
public static String formatDecimal(BigDecimal bd, int maxChars)
{
 String convert = bd.toString();
 String result=" ";
 char[] cArray = convert.toCharArray();
 int decPos = -1;
 int chrsBefore = 0;
 int chrsAfter = 0;
 int zeroesAfter = 0;
 int currentPos = 0;
 boolean zeroStreakAlive = true;

 if (cArray.length>maxChars){
    for (char c : cArray){
        if (c=='.')
            decPos = currentPos;
        else if (decPos == -1)
            chrsBefore++;
        else if (zeroStreakAlive && c=='0'){
            zeroesAfter++;
            chrsAfter++;   
        }
        else
            chrsAfter++;
        currentPos++;
    }

    if (chrsBefore>maxChars)
        result = cArray[0] + "." + cArray[1] + "E+" + (chrsBefore-1);
    else if (zeroesAfter >= maxChars-2)
        result = cArray[zeroesAfter+2] + "." + cArray[zeroesAfter+3] + "E-" + (chrsAfter-2);
    else 
        //your logic here probably using DecimalFormat for a normally rounded number to 6 places(including decimal point if one exists).
 }
 return result;
}
}

这就是我想出的。它并不是包罗万象的,因为有很多案例需要解决。我不确切知道您希望它们如何发挥作用,因此我无法全部完成,但是您应该能够获得想法并以此为起点。

于 2012-06-14T22:43:01.030 回答
1

如果您只想限制小数位数,一个简单的解决方案是

public static String formatDecimal(BigDecimal b, int max) {
    return b.setScale(max, RoundingMode.HALF_EVEN).stripTrailingZeros().toEngineeringString();
}

现在,您还想根据整数部分中的数字来限制小数位数。我认为使用 DecimalFormat 和 MessageFormat 类可以更好地发挥作用,这是我可以提供的,它通过了测试用例,但我不认为它那么健壮。您可以尝试创建一个更好的算法来处理所有不同的情况。

public static String formatDecimal(BigDecimal b, int max) {
    // trivial case
    String bs = b.stripTrailingZeros().toPlainString();
    if (bs.length() <= max) {
        return bs;
    }
    // determine the max integer = 1.0Emax
    String maxInteger = "1" + StringUtils.repeat("0", max - 1);
    // determine the min fraction = 1.0E-max
    String minFraction = "0." + StringUtils.repeat("0", max - 2) + "1";
    // get the integer part
    String integerPart = String.valueOf(b.intValue());
    // make the pattern like ###.### with the correct repetition
    String pattern = StringUtils.repeat("#", max - integerPart.length()) + "." + StringUtils.repeat("#", max - 1 - integerPart.length());
    // play with Message format, using a choice to determine when to use the exponential format
    MessageFormat fmt = new MessageFormat( //
            "{0,choice," + minFraction + "<{0,number,'0.#E0'}|0.1#{0,number,'" + pattern + "'}|" + maxInteger + "<{0,number,'0.#E0'}}" //
    );
    // time to format the number
    return fmt.format(new Object[] {b});
}

另一个使用 if/else 的解决方案可以像这样开始:

public static String formatDecimal(BigDecimal b, int max) {
    // trivial case
    if (b.toPlainString().length() <= max)
        return b.toPlainString();
    else {
        // between 0 and 1, or superior than 1*10^max, better use the exponential notation
        if ((b.compareTo(BigDecimal.ZERO) > 0 && b.compareTo(new BigDecimal("0.01")) < 0) //
                || (b.compareTo(new BigDecimal("1" + StringUtils.repeat("0", max - 1))) > 0)) {
            return new DecimalFormat("#.#E0").format(b);
        } else { // set Scale for fraction, better keep integer part safe
            String sb = b.toPlainString();
            return b.setScale(max - sb.indexOf(".") - 1, RoundingMode.HALF_EVEN).stripTrailingZeros().toPlainString();
        }
    }
}
于 2012-06-14T18:53:02.330 回答
0

我在这里用一种简单的蛮力方法回答我自己的问题,这让我比以前发布的那些方法更不紧张——我希望有一种我不知道的经过充分测试的现有方法。

不幸的是,我的解决方案不能很好地处理问题中的第三个测试,但我决定我可以忍受我的应用程序。事实上,我有 18 个字符可以玩,测试中 6 个字符的限制只是为了让测试更清晰。使用 18 个字符,尽管有前导零,我仍将获得足够的精度,直到 BigDecimal.toString() 在 10^-6 处切换到科学记数法。

此外,这种方法效率低下,但这对我的应用程序来说也不是问题。

public static String formatDecimal(BigDecimal number, int maxChars) {
    String s;
    int precision = maxChars;
    do {
        s = number
                .round(new MathContext(precision, RoundingMode.HALF_EVEN))
                .stripTrailingZeros()
                .toString();
        --precision;
    } while (s.length() > maxChars && precision > 0);
    return s;
}
于 2012-06-16T09:01:25.433 回答