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 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 に答える
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 に答える
0

私はここで自分の質問に答えています。以前に投稿したものよりも緊張を和らげる単純なブルートフォースアプローチを使用しています。これを行うための十分にテストされた既存の方法があり、私は知らなかったのです。

残念ながら、私のソリューションは質問の3番目のテストをうまく処理できませんが、アプリケーションでそれを使用できると判断しました。実際、私は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 に答える