diff --git a/pom.xml b/pom.xml
index c1b5ca8..ade4c43 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
- * 1K / 1M / 1B / 1T 단위로 표시하며 소수점 첫째 자리를 유지한다. (예: 1500 → "1.5K") + * 1K / 1M / 1B / 1T 단위로 표시하며 소수점 첫째 자리까지 남기고 나머지는 자른다. (예: 1760 → "1.7K") + * 반올림하지 않으므로 표시값이 실제 값을 넘지 않아, 레벨 임계값 근처에서도 숫자와 레벨이 어긋나지 않는다. * * @param tokens 변환할 토큰 수 * @return 단위 변환된 문자열 @@ -38,11 +39,17 @@ public static String escape(String s) { */ public static String formatTokens(long tokens) { return switch (Long.valueOf(tokens)) { - case Long l when l >= 1_000_000_000_000L -> String.format("%.1fT", l / 1_000_000_000_000.0); - case Long l when l >= 1_000_000_000L -> String.format("%.1fB", l / 1_000_000_000.0); - case Long l when l >= 1_000_000L -> String.format("%.1fM", l / 1_000_000.0); - case Long l when l >= 1_000L -> String.format("%.1fK", l / 1_000.0); + case Long l when l >= 1_000_000_000_000L -> truncate(l, 1_000_000_000_000L, "T"); + case Long l when l >= 1_000_000_000L -> truncate(l, 1_000_000_000L, "B"); + case Long l when l >= 1_000_000L -> truncate(l, 1_000_000L, "M"); + case Long l when l >= 1_000L -> truncate(l, 1_000L, "K"); default -> String.valueOf(tokens); }; } + + // 단위로 나눈 값을 소수 첫째 자리에서 자른다. 정수 연산이라 부동소수 오차가 없다. + private static String truncate(long tokens, long unit, String suffix) { + long tenths = tokens / (unit / 10); + return tenths / 10 + "." + tenths % 10 + suffix; + } } diff --git a/src/test/java/com/tokenphage/api/feature/badge/svg/SvgTextTest.java b/src/test/java/com/tokenphage/api/feature/badge/svg/SvgTextTest.java index a03938e..c529acb 100644 --- a/src/test/java/com/tokenphage/api/feature/badge/svg/SvgTextTest.java +++ b/src/test/java/com/tokenphage/api/feature/badge/svg/SvgTextTest.java @@ -23,6 +23,26 @@ void formatTokens_correctUnit(long tokens, String expected) { assertThat(SvgText.formatTokens(tokens)).isEqualTo(expected); } + @ParameterizedTest(name = "[{index}] {0} → {1}") + @DisplayName("소수 첫째 자리에서 자르고 반올림하지 않는다 — 표시값이 실제 값을 넘지 않는다") + @CsvSource({ + "1760000000, 1.7B", // 반올림이면 1.8B + "1960000000, 1.9B", // 반올림이면 2.0B + "999960000, 999.9M", // 반올림이면 1000.0M (단위 넘침) + "99960000, 99.9M", // Lv.3 임계(100M) 직전 — 반올림이면 100.0M + "999950, 999.9K", // 반올림이면 1000.0K + "999999999999, 999.9B" // T 단위 직전 — 반올림이면 1000.0B + }) + void 토큰표기_반올림경계값_소수첫째자리에서자름(long tokens, String expected) { + // given: 반올림하면 한 자리가 올라가는 경계값 + + // when + String actual = SvgText.formatTokens(tokens); + + // then: 레벨은 원값으로 판정하므로, 표시가 올라가면 임계값 근처에서 숫자와 레벨이 어긋난다 + assertThat(actual).isEqualTo(expected); + } + @ParameterizedTest @DisplayName("XML 특수문자를 이스케이프한다") @CsvSource({