根据最大字符限制优化格式化BigDecimal

时间:2012-06-14 18:28:42

标签: java bigdecimal

我想将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("#.####")可以正常工作,只要能够正确管理科学记数法。

3 个答案:

答案 0 :(得分: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();
        }
    }
}

答案 1 :(得分: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;
}
}

这就是我想出的。由于有很多案例需要解决,因此并非全部包含在内。我不确切地知道你希望他们如何发挥所以我不能完成所有这些,但你应该能够从中得到这个想法并从中开始。

答案 2 :(得分: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;
}