New SI formatting or something
This commit is contained in:
parent
5833448584
commit
73e0c9a57e
@ -1,5 +1,9 @@
|
||||
package ru.dbotthepony.mc.otm.core
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.TextComponent
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
fun BigInteger.formatReadableNumber(): String {
|
||||
@ -40,3 +44,239 @@ fun BigInteger.formatReadableNumber(): String {
|
||||
|
||||
return String(buffer)
|
||||
}
|
||||
|
||||
enum class SiPrefix(
|
||||
val power: Int,
|
||||
fractional: Boolean,
|
||||
val symbol: Char,
|
||||
) {
|
||||
// multiplies
|
||||
KILO (3, false, 'k'),
|
||||
MEGA (6, false, 'M'),
|
||||
GIGA (9, false, 'G'),
|
||||
TERA (12, false, 'T'),
|
||||
PETA (15, false, 'P'),
|
||||
EXA (18, false, 'E'),
|
||||
ZETTA(21, false, 'Z'),
|
||||
YOTTA(24, false, 'Y'),
|
||||
|
||||
// decimals
|
||||
DECI (1, true, 'd'),
|
||||
CENTI(2, true, 'c'),
|
||||
MILLI(3, true, 'm'),
|
||||
MICRO(6, true, 'μ'),
|
||||
NANO (9, true, 'n'),
|
||||
PICO (12, true, 'p'),
|
||||
FEMTO(15, true, 'f'),
|
||||
ATTO (18, true, 'a'),
|
||||
ZEPTO(21, true, 'z'),
|
||||
YOCTO(24, true, 'y');
|
||||
|
||||
val formatLocaleKey = "otm.suffix.${name.lowercase()}".intern()
|
||||
val rawLocaleKey = "otm.suffix_raw.${name.lowercase()}".intern()
|
||||
|
||||
val string = if (fractional) "0." + "0".repeat(power) else "1" + "0".repeat(power)
|
||||
|
||||
fun paddedIndex(input: String, index: Int): Char {
|
||||
val finalIndex = input.length - power + index
|
||||
|
||||
if (finalIndex >= 0) {
|
||||
return input[finalIndex]
|
||||
}
|
||||
|
||||
return '0'
|
||||
}
|
||||
|
||||
val decimal = BigDecimal(string)
|
||||
val fraction = Fraction(decimal)
|
||||
val impreciseFraction = ImpreciseFraction(string)
|
||||
val integer = if (!fractional) BigInteger(string) else null
|
||||
|
||||
companion object {
|
||||
val MULTIPLIES: List<SiPrefix> = ImmutableList.builder<SiPrefix>()
|
||||
.add(KILO)
|
||||
.add(MEGA)
|
||||
.add(GIGA)
|
||||
.add(TERA)
|
||||
.add(PETA)
|
||||
.add(EXA)
|
||||
.add(ZETTA)
|
||||
.add(YOTTA)
|
||||
.build()
|
||||
|
||||
val DECIMALS: List<SiPrefix> = ImmutableList.builder<SiPrefix>()
|
||||
.add(DECI)
|
||||
.add(CENTI)
|
||||
.add(MILLI)
|
||||
.add(MICRO)
|
||||
.add(NANO)
|
||||
.add(PICO)
|
||||
.add(FEMTO)
|
||||
.add(ATTO)
|
||||
.add(ZEPTO)
|
||||
.add(YOCTO)
|
||||
.build()
|
||||
|
||||
val DECIMALS_IMPRECISE: List<SiPrefix> = ImmutableList.builder<SiPrefix>()
|
||||
.add(DECI)
|
||||
.add(CENTI)
|
||||
.add(MILLI)
|
||||
.add(MICRO)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatSi(si: SiPrefix, divided: String, remainder: String, decimalPlaces: Int, isNegative: Boolean): String {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val decimalPlaces = decimalPlaces.coerceAtMost(si.power)
|
||||
|
||||
val add = (if (isNegative) 1 else 0)
|
||||
|
||||
val buffer = CharArray(divided.length + 2 + decimalPlaces + add)
|
||||
buffer[buffer.size - 1] = si.symbol
|
||||
|
||||
if (isNegative) {
|
||||
buffer[0] = '-'
|
||||
}
|
||||
|
||||
for (i in divided.indices) {
|
||||
buffer[add + i] = divided[i]
|
||||
}
|
||||
|
||||
buffer[add + divided.length] = '.'
|
||||
|
||||
for (i in 0 until decimalPlaces) {
|
||||
buffer[add + i + divided.length + 1] = si.paddedIndex(remainder, i)
|
||||
}
|
||||
|
||||
return String(buffer)
|
||||
}
|
||||
|
||||
fun BigDecimal.determineSiPrefix(): SiPrefix? {
|
||||
if (isZero) {
|
||||
return null
|
||||
}
|
||||
|
||||
var num = this
|
||||
|
||||
if (isNegative) {
|
||||
num = -this
|
||||
}
|
||||
|
||||
var prev: SiPrefix? = null
|
||||
|
||||
if (num >= BigDecimal.ONE) {
|
||||
for (value in SiPrefix.MULTIPLIES) {
|
||||
if (value.decimal <= num) {
|
||||
prev = value
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (value in SiPrefix.DECIMALS) {
|
||||
if (value.decimal >= num) {
|
||||
prev = value
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prev
|
||||
}
|
||||
|
||||
fun BigDecimal.formatSiTranslatable(): Component {
|
||||
if (isZero) {
|
||||
return TextComponent("0.00")
|
||||
} else if (this == BigDecimal.ONE) {
|
||||
return TextComponent("1.00")
|
||||
}
|
||||
|
||||
return TextComponent("1.00")
|
||||
}
|
||||
|
||||
fun BigInteger.determineSiPrefix(): SiPrefix? {
|
||||
if (isZero) {
|
||||
return null
|
||||
}
|
||||
|
||||
var num = this
|
||||
|
||||
if (isNegative) {
|
||||
num = -this
|
||||
}
|
||||
|
||||
var prev: SiPrefix? = null
|
||||
|
||||
if (num >= BigInteger.ONE) {
|
||||
for (value in SiPrefix.MULTIPLIES) {
|
||||
if (value.integer!! <= num) {
|
||||
prev = value
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prev
|
||||
}
|
||||
|
||||
fun BigInteger.formatSi(decimalPlaces: Int = 2): String {
|
||||
require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" }
|
||||
val prefix = determineSiPrefix() ?: return toString() // + "." + "0".repeat(decimalPlaces)
|
||||
val isNegative = isNegative
|
||||
val arr = (if (isNegative) -this else this).divideAndRemainder(prefix.integer)
|
||||
val divided = arr[0]
|
||||
val remainder = arr[1]
|
||||
|
||||
if (decimalPlaces == 0) {
|
||||
if (isNegative) {
|
||||
return "-" + divided.toString() + prefix.symbol
|
||||
} else {
|
||||
return divided.toString() + prefix.symbol
|
||||
}
|
||||
}
|
||||
|
||||
return formatSi(prefix, divided.toString(), remainder.toString(), decimalPlaces, isNegative)
|
||||
}
|
||||
|
||||
fun ImpreciseFraction.determineSiPrefix(): SiPrefix? {
|
||||
if (isZero) {
|
||||
return null
|
||||
}
|
||||
|
||||
var num = this
|
||||
|
||||
if (isNegative) {
|
||||
num = -this
|
||||
}
|
||||
|
||||
var prev: SiPrefix? = null
|
||||
|
||||
if (num >= ImpreciseFraction.ONE) {
|
||||
for (value in SiPrefix.MULTIPLIES) {
|
||||
if (value.impreciseFraction <= num) {
|
||||
prev = value
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (value in SiPrefix.DECIMALS_IMPRECISE) {
|
||||
if (value.impreciseFraction >= num) {
|
||||
prev = value
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prev
|
||||
}
|
||||
|
||||
fun ImpreciseFraction.formatSi(decimalPlaces: Int = 2): String {
|
||||
require(decimalPlaces >= 0) { "Invalid amount of decimal places required: $decimalPlaces" }
|
||||
val prefix = determineSiPrefix() ?: return toString(decimalPlaces)
|
||||
return (this / prefix.impreciseFraction).toString(decimalPlaces) + prefix.symbol
|
||||
}
|
||||
|
@ -75,6 +75,27 @@ private fun bytesToLongBE(
|
||||
*/
|
||||
const val EPSILON = 0.000000000001
|
||||
|
||||
private val zeroes = Array(16) { "0".repeat(it).intern() }
|
||||
private val nums = Array(10) { it.toString()[0] }
|
||||
|
||||
private fun decimals(input: Double, places: Int): String {
|
||||
if (weakEqualDoubles(input, 0.0)) {
|
||||
return zeroes.elementAtOrElse(places) { "0".repeat(places) }
|
||||
}
|
||||
|
||||
val buffer = CharArray(places)
|
||||
|
||||
@Suppress("NAME_SHADOWING")
|
||||
var input = input.absoluteValue
|
||||
|
||||
for (i in 0 until places) {
|
||||
input *= 10.0
|
||||
buffer[i] = nums[(input % 10.0).toInt()]
|
||||
}
|
||||
|
||||
return String(buffer)
|
||||
}
|
||||
|
||||
fun weakEqualDoubles(a: Double, b: Double): Boolean {
|
||||
if (a == b)
|
||||
return true
|
||||
@ -370,27 +391,12 @@ class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Do
|
||||
if (decimals == 0) {
|
||||
return whole.toString()
|
||||
} else if (decimals > 0) {
|
||||
val strDecimal = if (decimal == 0.0 || decimal == -0.0 || decimal > 0.0 && decimal < 1E-11 || decimal < 0.0 && decimal > -1E-11) {
|
||||
"0"
|
||||
} else if (decimal > 0.0) {
|
||||
decimal.toString().substring(2)
|
||||
} else {
|
||||
decimal.toString().substring(3)
|
||||
}
|
||||
|
||||
if (strDecimal.length < decimals) {
|
||||
return "$whole.${strDecimal + "0".repeat(decimals - strDecimal.length)}"
|
||||
} else if (strDecimal.length > decimals) {
|
||||
return "$whole.${strDecimal.substring(0, decimals)}"
|
||||
} else {
|
||||
return "$whole.$strDecimal"
|
||||
}
|
||||
return "$whole.${decimals(decimal, decimals)}"
|
||||
}
|
||||
|
||||
return when (signum()) {
|
||||
1 -> "$whole.${decimal.toString().substring(2)}"
|
||||
1, -1 -> "$whole.${decimals(decimal, 11)}"
|
||||
0 -> "0.0"
|
||||
-1 -> "$whole.${decimal.toString().substring(3)}"
|
||||
else -> throw IllegalArgumentException("invalid signum")
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,16 @@
|
||||
package ru.dbotthepony.mc.otm.core
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
inline val BigInteger.isZero get() = this == BigInteger.ZERO
|
||||
inline val BigInteger.isPositive get() = this > BigInteger.ZERO
|
||||
inline val BigInteger.isNegative get() = this < BigInteger.ZERO
|
||||
|
||||
inline val BigDecimal.isZero get() = this == BigDecimal.ZERO
|
||||
inline val BigDecimal.isPositive get() = this > BigDecimal.ZERO
|
||||
inline val BigDecimal.isNegative get() = this < BigDecimal.ZERO
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
fun equalDownDivision(a: Int, b: Int): Int {
|
||||
if (a % b == 0) {
|
||||
|
@ -3,12 +3,14 @@ package ru.dbotthepony.mc.otm.tests
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.DisplayName
|
||||
import org.junit.jupiter.api.Test
|
||||
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
|
||||
import ru.dbotthepony.mc.otm.core.formatReadableNumber
|
||||
import ru.dbotthepony.mc.otm.core.formatSi
|
||||
import java.math.BigInteger
|
||||
|
||||
object FormattingTests {
|
||||
@Test
|
||||
@DisplayName("BigInteger formatting")
|
||||
@DisplayName("BigInteger formatting as readable number")
|
||||
fun biginteger() {
|
||||
assertEquals("0", BigInteger("0").formatReadableNumber())
|
||||
assertEquals("45", BigInteger("45").formatReadableNumber())
|
||||
@ -29,4 +31,39 @@ object FormattingTests {
|
||||
assertEquals("2 730 250 200", BigInteger("2730250200").formatReadableNumber())
|
||||
assertEquals("1 222 730 250 200", BigInteger("1222730250200").formatReadableNumber())
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("BigInteger formatting as si number")
|
||||
fun bigintegerSi() {
|
||||
assertEquals("0", BigInteger("0").formatSi())
|
||||
assertEquals("420", BigInteger("420").formatSi())
|
||||
assertEquals("-420", BigInteger("-420").formatSi())
|
||||
assertEquals("555", BigInteger("555").formatSi())
|
||||
assertEquals("55", BigInteger("55").formatSi())
|
||||
assertEquals("1.20k", BigInteger("1205").formatSi())
|
||||
assertEquals("-1.20k", BigInteger("-1205").formatSi())
|
||||
assertEquals("1.21k", BigInteger("1215").formatSi())
|
||||
assertEquals("4.50M", BigInteger("4501204").formatSi())
|
||||
assertEquals("4.00M", BigInteger("4000111").formatSi())
|
||||
assertEquals("4.0011M", BigInteger("4001111").formatSi(4))
|
||||
assertEquals("-4.0011M", BigInteger("-4001111").formatSi(4))
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ImpreciseFraction formatting as si number")
|
||||
fun impreciseFractionSi() {
|
||||
assertEquals("0.00", ImpreciseFraction("0").formatSi(2))
|
||||
assertEquals("14.62", ImpreciseFraction("14.62").formatSi(2))
|
||||
assertEquals("1.00k", ImpreciseFraction("1000").formatSi(2))
|
||||
assertEquals("1.00k", ImpreciseFraction("1000.1").formatSi(2))
|
||||
assertEquals("1.00k", ImpreciseFraction("1004.2").formatSi(2))
|
||||
assertEquals("1.01k", ImpreciseFraction("1014.5").formatSi(2))
|
||||
assertEquals("1.014k", ImpreciseFraction("1014.5").formatSi(3))
|
||||
assertEquals("1.01k", ImpreciseFraction("1014.256").formatSi(2))
|
||||
assertEquals("12.73k", ImpreciseFraction("12734.256").formatSi(2))
|
||||
assertEquals("127.34k", ImpreciseFraction("127342.256").formatSi(2))
|
||||
assertEquals("1.27M", ImpreciseFraction("1273421.256").formatSi(2))
|
||||
assertEquals("1.273M", ImpreciseFraction("1273421.256").formatSi(3))
|
||||
assertEquals("1.2734M", ImpreciseFraction("1273421.256").formatSi(4))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user