New SI formatting or something
This commit is contained in:
parent
5833448584
commit
73e0c9a57e
@ -1,5 +1,9 @@
|
|||||||
package ru.dbotthepony.mc.otm.core
|
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
|
import java.math.BigInteger
|
||||||
|
|
||||||
fun BigInteger.formatReadableNumber(): String {
|
fun BigInteger.formatReadableNumber(): String {
|
||||||
@ -40,3 +44,239 @@ fun BigInteger.formatReadableNumber(): String {
|
|||||||
|
|
||||||
return String(buffer)
|
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
|
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 {
|
fun weakEqualDoubles(a: Double, b: Double): Boolean {
|
||||||
if (a == b)
|
if (a == b)
|
||||||
return true
|
return true
|
||||||
@ -370,27 +391,12 @@ class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Do
|
|||||||
if (decimals == 0) {
|
if (decimals == 0) {
|
||||||
return whole.toString()
|
return whole.toString()
|
||||||
} else if (decimals > 0) {
|
} 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) {
|
return "$whole.${decimals(decimal, decimals)}"
|
||||||
"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 when (signum()) {
|
return when (signum()) {
|
||||||
1 -> "$whole.${decimal.toString().substring(2)}"
|
1, -1 -> "$whole.${decimals(decimal, 11)}"
|
||||||
0 -> "0.0"
|
0 -> "0.0"
|
||||||
-1 -> "$whole.${decimal.toString().substring(3)}"
|
|
||||||
else -> throw IllegalArgumentException("invalid signum")
|
else -> throw IllegalArgumentException("invalid signum")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
package ru.dbotthepony.mc.otm.core
|
package ru.dbotthepony.mc.otm.core
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
|
||||||
inline val BigInteger.isZero get() = this == BigInteger.ZERO
|
inline val BigInteger.isZero get() = this == BigInteger.ZERO
|
||||||
inline val BigInteger.isPositive get() = this > BigInteger.ZERO
|
inline val BigInteger.isPositive get() = this > BigInteger.ZERO
|
||||||
inline val BigInteger.isNegative 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")
|
@Suppress("SameParameterValue")
|
||||||
fun equalDownDivision(a: Int, b: Int): Int {
|
fun equalDownDivision(a: Int, b: Int): Int {
|
||||||
if (a % b == 0) {
|
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.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
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.formatReadableNumber
|
||||||
|
import ru.dbotthepony.mc.otm.core.formatSi
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
|
||||||
object FormattingTests {
|
object FormattingTests {
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("BigInteger formatting")
|
@DisplayName("BigInteger formatting as readable number")
|
||||||
fun biginteger() {
|
fun biginteger() {
|
||||||
assertEquals("0", BigInteger("0").formatReadableNumber())
|
assertEquals("0", BigInteger("0").formatReadableNumber())
|
||||||
assertEquals("45", BigInteger("45").formatReadableNumber())
|
assertEquals("45", BigInteger("45").formatReadableNumber())
|
||||||
@ -29,4 +31,39 @@ object FormattingTests {
|
|||||||
assertEquals("2 730 250 200", BigInteger("2730250200").formatReadableNumber())
|
assertEquals("2 730 250 200", BigInteger("2730250200").formatReadableNumber())
|
||||||
assertEquals("1 222 730 250 200", BigInteger("1222730250200").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