Skip to content

Commit 2f01fe8

Browse files
committed
Hot fix for compact numbers...
1 parent 4c13bae commit 2f01fe8

1 file changed

Lines changed: 30 additions & 48 deletions

File tree

  • src/main/kotlin/com/github/encryptsl/lite/eco/common/extensions
Lines changed: 30 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.encryptsl.lite.eco.common.extensions
22
import java.math.BigDecimal
3+
import java.math.BigInteger
34
import java.math.RoundingMode
45
import java.text.DecimalFormat
56
import java.text.DecimalFormatSymbols
@@ -9,53 +10,42 @@ private val units = arrayOf(
910
"", "K", "M", "B", "T", "Q", "Qn", "S", "Se", "O", "N", "D"
1011
)
1112

12-
/**
13-
* Converts a string to BigDecimal, including truncated units (e.g., "1.5M" → 1500000).
14-
*/
1513
fun String.toValidDecimal(): BigDecimal? {
16-
if (this.isBlank() || this.contains(" ")) return null
17-
return decompressNumber(this)
14+
val s = this.trim()
15+
if (s.isBlank() || s.contains(" ")) return null
16+
return decompressNumber(s)
1817
}
1918

20-
/**
21-
* Returns a number in abbreviated format (e.g., 1500 → "1.5K").
22-
*/
2319
fun BigDecimal.compactFormat(pattern: String, compactPattern: String, locale: String): String {
24-
val (value, unit) = compactNumber(this) ?: (null to null)
25-
return value?.let {
26-
formatNumber(it, compactPattern, locale, compacted = true) + unit
27-
} ?: formatNumber(this, pattern, locale)
20+
val pair = compactNumber(this)
21+
return if (pair != null) {
22+
val (value, unit) = pair
23+
formatNumber(value, compactPattern, locale, compacted = true) + unit
24+
} else {
25+
formatNumber(this, pattern, locale)
26+
}
2827
}
2928

30-
/**
31-
* Returns a number as currency according to the specified pattern and locale.
32-
*/
3329
fun BigDecimal.moneyFormat(pattern: String, locale: String): String {
3430
return formatNumber(this, pattern, locale)
3531
}
3632

37-
/**
38-
* Formats a number according to a pattern and locale.
39-
*/
4033
private fun formatNumber(number: BigDecimal, pattern: String, locale: String, compacted: Boolean = false): String {
4134
val formatter = DecimalFormat().apply {
4235
decimalFormatSymbols = DecimalFormatSymbols.getInstance(getLocale(locale))
43-
roundingMode = if (compacted) RoundingMode.UNNECESSARY else RoundingMode.HALF_UP
36+
// always safe rounding; UNNECESSARY caused exceptions
37+
roundingMode = if (compacted) RoundingMode.HALF_UP else RoundingMode.HALF_UP
4438
applyPattern(pattern)
4539
}
4640
return formatter.format(number)
4741
}
4842

49-
/**
50-
* Decompress string → BigDecimal (e.g., "2.5M" → 2500000).
51-
*/
5243
private fun decompressNumber(str: String): BigDecimal? {
53-
val upper = str.uppercase(Locale.getDefault())
44+
val upper = str.trim().uppercase(Locale.getDefault())
5445

55-
// find suffix
5646
val unit = units
5747
.filter { it.isNotEmpty() }
58-
.sortedByDescending { it.length } // so that "Qn" takes precedence over "Q"
48+
.sortedByDescending { it.length }
5949
.firstOrNull { upper.endsWith(it) }
6050

6151
return if (unit != null) {
@@ -67,36 +57,28 @@ private fun decompressNumber(str: String): BigDecimal? {
6757
}
6858
}
6959

70-
/**
71-
* Compact BigDecimal → Pair(value, unit).
72-
*/
7360
private fun compactNumber(number: BigDecimal): Pair<BigDecimal, String>? {
74-
if (number == BigDecimal.ZERO) return null
61+
if (number.compareTo(BigDecimal.ZERO) == 0) return null
7562

76-
val absNum = number.abs()
77-
val exp = absNum.toBigInteger().toString().length - 1 // log10 over string length
78-
val unitIndex = exp / 3
63+
// use integer part to determine number of digits -> stable decision unit
64+
val absIntPart: BigInteger = number.abs().toBigInteger()
65+
if (absIntPart == BigInteger.ZERO) return null
7966

67+
val digits = absIntPart.toString().length
68+
val unitIndex = (digits - 1) / 3
8069
if (unitIndex == 0) return null
8170

82-
if (unitIndex >= units.size) {
83-
// fallback: use the last known unit
84-
val divisor = BigDecimal.TEN.pow((units.size - 1) * 3)
85-
return number.divide(divisor) to units.last()
86-
}
71+
val index = if (unitIndex >= units.size) units.size - 1 else unitIndex
72+
val divisor = BigDecimal.TEN.pow(index * 3)
8773

88-
val divisor = BigDecimal.TEN.pow(unitIndex * 3)
89-
return number.divide(divisor) to units[unitIndex]
74+
// scale = 1 -> display up to 1 decimal place (for 44_888 -> 44.9K)
75+
// If you always want no decimal places (44K), use scale = 0 and RoundingMode.DOWN
76+
val scaled = number.divide(divisor, 1, RoundingMode.HALF_UP)
77+
78+
return scaled to units[index]
9079
}
9180

92-
/**
93-
* Returns the locale based on a string (e.g., "en", "en-US", "zh_CN").
94-
*/
9581
private fun getLocale(localeStr: String): Locale {
96-
val parts = localeStr.split("-", "_")
97-
return when (parts.size) {
98-
1 -> Locale.of(parts[0])
99-
2 -> Locale.of(parts[0], parts[1])
100-
else -> Locale.of(parts[0], parts[1], parts[2])
101-
}
82+
// compatible and safe: "en", "en-US", "zh_CN" -> "en", "en-US", "zh-CN"
83+
return Locale.forLanguageTag(localeStr.replace('_', '-'))
10284
}

0 commit comments

Comments
 (0)