Um meinem Kommentar treu zu bleiben, dass ich die Lesbarkeit über die Leistung stellen würde, ist hier eine Version, in der klar sein sollte, was passiert (vorausgesetzt, Sie haben BigDecimal
s zuvor verwendet), ohne übermäßige Kommentare abzugeben (ich glaube an selbstdokumentierenden Code), ohne sich um die Leistung zu sorgen (Da ich mir kein Szenario vorstellen kann, in dem Sie dies so viele Millionen Mal tun möchten, dass die Leistung sogar in Betracht gezogen wird).
Diese Version:
- verwendet
BigDecimal
s zur Präzision und zur Vermeidung von Rundungsproblemen
- arbeitet zum Abrunden, wie vom OP gefordert
- funktioniert für andere Rundungsmodi, zB
HALF_UP
wie in den Tests
- ermöglicht es Ihnen, die Genauigkeit anzupassen (ändern
REQUIRED_PRECISION
)
- verwendet a
enum
, um die Schwellenwerte zu definieren, dh kann leicht angepasst werden, um KB / MB / GB / TB anstelle von k / m / b / t usw. zu verwenden, und kann natürlich bei Bedarf darüber hinaus erweitert TRILLION
werden
- kommt mit gründlichen Unit-Tests, da die Testfälle in der Frage nicht die Grenzen getestet haben
- sollte für Null und negative Zahlen funktionieren
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;
}
}
(Kommentieren Sie die println
Anweisungen aus oder ändern Sie sie, um Ihren bevorzugten Logger zu verwenden, um zu sehen, was er tut.)
Und schließlich die Tests in NumberShortenerTest (Plain 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));
}
}
}
Fühlen Sie sich frei, in den Kommentaren darauf hinzuweisen, wenn ich einen signifikanten Testfall verpasst habe oder wenn die erwarteten Werte angepasst werden sollten.