坚持我的看法,即我将可读性放在性能之上,这是一个版本,应该清楚发生了什么(假设您已经使用过) BigDecimal
s),而无需过多评论(我相信自己编写的代码),而不必担心性能(因为我无法想象一个场景,在这种情况下,您想执行此操作数百万次,因此甚至需要考虑性能)。
这个版本:
- 使用
BigDecimal
s表示精度并避免舍入问题
- 根据OP的要求进行四舍五入
- 适用于其他舍入模式,例如
HALF_UP
在测试中
- 允许您调整精度(更改
REQUIRED_PRECISION
)
- 使用
enum
来定义阈值,即可以轻松地调整为使用KB / MB / GB / TB代替k / m / b / t等,并且当然可以TRILLION
根据需要进行扩展
- 带有全面的单元测试,因为问题中的测试用例没有测试边界
- 应该适用于零和负数
Threshold.java:
import java.math.BigDecimal;
public enum Threshold {
TRILLION("1000000000000", 12, 't', null),
BILLION("1000000000", 9, 'b', TRILLION),
MILLION("1000000", 6, 'm', BILLION),
THOUSAND("1000", 3, 'k', MILLION),
ZERO("0", 0, null, THOUSAND);
private BigDecimal value;
private int zeroes;
protected Character suffix;
private Threshold higherThreshold;
private Threshold(String aValueString, int aNumberOfZeroes, Character aSuffix,
Threshold aThreshold) {
value = new BigDecimal(aValueString);
zeroes = aNumberOfZeroes;
suffix = aSuffix;
higherThreshold = aThreshold;
}
public static Threshold thresholdFor(long aValue) {
return thresholdFor(new BigDecimal(aValue));
}
public static Threshold thresholdFor(BigDecimal aValue) {
for (Threshold eachThreshold : Threshold.values()) {
if (eachThreshold.value.compareTo(aValue) <= 0) {
return eachThreshold;
}
}
return TRILLION; // shouldn't be needed, but you might have to extend the enum
}
public int getNumberOfZeroes() {
return zeroes;
}
public String getSuffix() {
return suffix == null ? "" : "" + suffix;
}
public Threshold getHigherThreshold() {
return higherThreshold;
}
}
NumberShortener.java:
import java.math.BigDecimal;
import java.math.RoundingMode;
public class NumberShortener {
public static final int REQUIRED_PRECISION = 2;
public static BigDecimal toPrecisionWithoutLoss(BigDecimal aBigDecimal,
int aPrecision, RoundingMode aMode) {
int previousScale = aBigDecimal.scale();
int previousPrecision = aBigDecimal.precision();
int newPrecision = Math.max(previousPrecision - previousScale, aPrecision);
return aBigDecimal.setScale(previousScale + newPrecision - previousPrecision,
aMode);
}
private static BigDecimal scaledNumber(BigDecimal aNumber, RoundingMode aMode) {
Threshold threshold = Threshold.thresholdFor(aNumber);
BigDecimal adjustedNumber = aNumber.movePointLeft(threshold.getNumberOfZeroes());
BigDecimal scaledNumber = toPrecisionWithoutLoss(adjustedNumber, REQUIRED_PRECISION,
aMode).stripTrailingZeros();
// System.out.println("Number: <" + aNumber + ">, adjusted: <" + adjustedNumber
// + ">, rounded: <" + scaledNumber + ">");
return scaledNumber;
}
public static String shortenedNumber(long aNumber, RoundingMode aMode) {
boolean isNegative = aNumber < 0;
BigDecimal numberAsBigDecimal = new BigDecimal(isNegative ? -aNumber : aNumber);
Threshold threshold = Threshold.thresholdFor(numberAsBigDecimal);
BigDecimal scaledNumber = aNumber == 0 ? numberAsBigDecimal : scaledNumber(
numberAsBigDecimal, aMode);
if (scaledNumber.compareTo(new BigDecimal("1000")) >= 0) {
scaledNumber = scaledNumber(scaledNumber, aMode);
threshold = threshold.getHigherThreshold();
}
String sign = isNegative ? "-" : "";
String printNumber = sign + scaledNumber.stripTrailingZeros().toPlainString()
+ threshold.getSuffix();
// System.out.println("Number: <" + sign + numberAsBigDecimal + ">, rounded: <"
// + sign + scaledNumber + ">, print: <" + printNumber + ">");
return printNumber;
}
}
(取消注释该println
语句,或更改为使用您喜欢的记录器查看其运行情况。)
最后,NumberShortenerTest(纯JUnit 4)中的测试:
import static org.junit.Assert.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import org.junit.Test;
public class NumberShortenerTest {
private static final long[] NUMBERS_FROM_OP = new long[] { 1000, 5821, 10500, 101800, 2000000, 7800000, 92150000, 123200000 };
private static final String[] EXPECTED_FROM_OP = new String[] { "1k", "5.8k", "10k", "101k", "2m", "7.8m", "92m", "123m" };
private static final String[] EXPECTED_FROM_OP_HALF_UP = new String[] { "1k", "5.8k", "11k", "102k", "2m", "7.8m", "92m", "123m" };
private static final long[] NUMBERS_TO_TEST = new long[] { 1, 500, 999, 1000, 1001, 1009, 1049, 1050, 1099, 1100, 12345, 123456, 999999, 1000000,
1000099, 1000999, 1009999, 1099999, 1100000, 1234567, 999999999, 1000000000, 9123456789L, 123456789123L };
private static final String[] EXPECTED_FROM_TEST = new String[] { "1", "500", "999", "1k", "1k", "1k", "1k", "1k", "1k", "1.1k", "12k", "123k",
"999k", "1m", "1m", "1m", "1m", "1m", "1.1m", "1.2m", "999m", "1b", "9.1b", "123b" };
private static final String[] EXPECTED_FROM_TEST_HALF_UP = new String[] { "1", "500", "999", "1k", "1k", "1k", "1k", "1.1k", "1.1k", "1.1k", "12k",
"123k", "1m", "1m", "1m", "1m", "1m", "1.1m", "1.1m", "1.2m", "1b", "1b", "9.1b", "123b" };
@Test
public void testThresholdFor() {
assertEquals(Threshold.ZERO, Threshold.thresholdFor(1));
assertEquals(Threshold.ZERO, Threshold.thresholdFor(999));
assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(1000));
assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(1234));
assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(9999));
assertEquals(Threshold.THOUSAND, Threshold.thresholdFor(999999));
assertEquals(Threshold.MILLION, Threshold.thresholdFor(1000000));
}
@Test
public void testToPrecision() {
RoundingMode mode = RoundingMode.DOWN;
assertEquals(new BigDecimal("1"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 1, mode));
assertEquals(new BigDecimal("1.2"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 2, mode));
assertEquals(new BigDecimal("1.23"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 3, mode));
assertEquals(new BigDecimal("1.234"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 4, mode));
assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 4, mode).stripTrailingZeros()
.toPlainString());
assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 2, mode).stripTrailingZeros()
.toPlainString());
assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999.9"), 2, mode).stripTrailingZeros()
.toPlainString());
mode = RoundingMode.HALF_UP;
assertEquals(new BigDecimal("1"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 1, mode));
assertEquals(new BigDecimal("1.2"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 2, mode));
assertEquals(new BigDecimal("1.23"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 3, mode));
assertEquals(new BigDecimal("1.235"), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("1.23456"), 4, mode));
assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 4, mode).stripTrailingZeros()
.toPlainString());
assertEquals(new BigDecimal("999").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999"), 2, mode).stripTrailingZeros()
.toPlainString());
assertEquals(new BigDecimal("1000").toPlainString(), NumberShortener.toPrecisionWithoutLoss(new BigDecimal("999.9"), 2, mode)
.stripTrailingZeros().toPlainString());
}
@Test
public void testNumbersFromOP() {
for (int i = 0; i < NUMBERS_FROM_OP.length; i++) {
assertEquals("Index " + i + ": " + NUMBERS_FROM_OP[i], EXPECTED_FROM_OP[i],
NumberShortener.shortenedNumber(NUMBERS_FROM_OP[i], RoundingMode.DOWN));
assertEquals("Index " + i + ": " + NUMBERS_FROM_OP[i], EXPECTED_FROM_OP_HALF_UP[i],
NumberShortener.shortenedNumber(NUMBERS_FROM_OP[i], RoundingMode.HALF_UP));
}
}
@Test
public void testBorders() {
assertEquals("Zero: " + 0, "0", NumberShortener.shortenedNumber(0, RoundingMode.DOWN));
assertEquals("Zero: " + 0, "0", NumberShortener.shortenedNumber(0, RoundingMode.HALF_UP));
for (int i = 0; i < NUMBERS_TO_TEST.length; i++) {
assertEquals("Index " + i + ": " + NUMBERS_TO_TEST[i], EXPECTED_FROM_TEST[i],
NumberShortener.shortenedNumber(NUMBERS_TO_TEST[i], RoundingMode.DOWN));
assertEquals("Index " + i + ": " + NUMBERS_TO_TEST[i], EXPECTED_FROM_TEST_HALF_UP[i],
NumberShortener.shortenedNumber(NUMBERS_TO_TEST[i], RoundingMode.HALF_UP));
}
}
@Test
public void testNegativeBorders() {
for (int i = 0; i < NUMBERS_TO_TEST.length; i++) {
assertEquals("Index " + i + ": -" + NUMBERS_TO_TEST[i], "-" + EXPECTED_FROM_TEST[i],
NumberShortener.shortenedNumber(-NUMBERS_TO_TEST[i], RoundingMode.DOWN));
assertEquals("Index " + i + ": -" + NUMBERS_TO_TEST[i], "-" + EXPECTED_FROM_TEST_HALF_UP[i],
NumberShortener.shortenedNumber(-NUMBERS_TO_TEST[i], RoundingMode.HALF_UP));
}
}
}
如果我错过了重要的测试用例或应该调整期望值,请随时在评论中指出。