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