New Decimal class, with fixed point and compile-time precision

This commit is contained in:
DBotThePony 2023-06-18 17:45:48 +07:00
parent 1c7abed6e9
commit 48ee669458
Signed by: DBot
GPG Key ID: DCC23B5715498507
4 changed files with 521 additions and 502 deletions

View File

@ -4,7 +4,7 @@ import mekanism.api.math.FloatingLong
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import java.math.BigInteger import java.math.BigInteger
private val LONG_OVERFLOW = BigInteger.valueOf(Long.MAX_VALUE) + BigInteger.valueOf(Long.MAX_VALUE) + BigInteger.TWO private val LONG_OVERFLOW = (BigInteger.valueOf(Long.MAX_VALUE) + BigInteger.valueOf(Long.MAX_VALUE) + BigInteger.TWO) * Decimal.PRECISION_POW_BI
private val LONG_OVERFLOW1 = BigInteger.valueOf(Long.MAX_VALUE) + BigInteger.valueOf(Long.MAX_VALUE) + BigInteger.ONE private val LONG_OVERFLOW1 = BigInteger.valueOf(Long.MAX_VALUE) + BigInteger.valueOf(Long.MAX_VALUE) + BigInteger.ONE
fun Decimal.toFloatingLong(): FloatingLong { fun Decimal.toFloatingLong(): FloatingLong {
@ -17,14 +17,16 @@ fun Decimal.toFloatingLong(): FloatingLong {
return FloatingLong.MAX_VALUE return FloatingLong.MAX_VALUE
} }
return FloatingLong.create(whole.toLong(), (decimal * 10_000.0).toInt().toShort()) return FloatingLong.create(whole.toLong(), (fractionalFloat * 10_000.0).toInt().toShort())
} }
fun FloatingLong.toDecimal(): Decimal { fun FloatingLong.toDecimal(): Decimal {
var conv = BigInteger.valueOf(value) * Decimal.PRECISION_POW_BI + BigInteger.valueOf((decimal.toDouble() / 10_000.0 * Decimal.PRECISION_DOUBLE).toLong())
// Overflow // Overflow
if (value < 0L) { if (value < 0L) {
return Decimal(LONG_OVERFLOW + BigInteger.valueOf(value), decimal / 10_000.0) conv += LONG_OVERFLOW
} }
return Decimal(value, decimal / 10_000.0) return Decimal.raw(conv)
} }

File diff suppressed because it is too large Load Diff

View File

@ -2,14 +2,15 @@ package ru.dbotthepony.mc.otm.core.math
import java.math.BigDecimal import java.math.BigDecimal
import java.math.BigInteger import java.math.BigInteger
import kotlin.math.absoluteValue
inline val BigInteger.isZero get() = this == BigInteger.ZERO inline val BigInteger.isZero get() = this.signum() == 0
inline val BigInteger.isPositive get() = this > BigInteger.ZERO inline val BigInteger.isPositive get() = this.signum() > 0
inline val BigInteger.isNegative get() = this < BigInteger.ZERO inline val BigInteger.isNegative get() = this.signum() < 0
inline val BigDecimal.isZero get() = this == BigDecimal.ZERO inline val BigDecimal.isZero get() = this.signum() == 0
inline val BigDecimal.isPositive get() = this > BigDecimal.ZERO inline val BigDecimal.isPositive get() = this.signum() > 0
inline val BigDecimal.isNegative get() = this < BigDecimal.ZERO inline val BigDecimal.isNegative get() = this.signum() < 0
val BI_INT_MAX: BigInteger = BigInteger.valueOf(Int.MAX_VALUE.toLong()) val BI_INT_MAX: BigInteger = BigInteger.valueOf(Int.MAX_VALUE.toLong())
val BI_INT_MIN: BigInteger = BigInteger.valueOf(Int.MIN_VALUE.toLong()) val BI_INT_MIN: BigInteger = BigInteger.valueOf(Int.MIN_VALUE.toLong())
@ -21,6 +22,35 @@ val BI_FLOAT_MIN: BigInteger = BigDecimal(Float.MIN_VALUE.toString()).toBigInteg
val BI_DOUBLE_MAX: BigInteger = BigDecimal(Double.MAX_VALUE.toString()).toBigInteger() val BI_DOUBLE_MAX: BigInteger = BigDecimal(Double.MAX_VALUE.toString()).toBigInteger()
val BI_DOUBLE_MIN: BigInteger = BigDecimal(Double.MIN_VALUE.toString()).toBigInteger() val BI_DOUBLE_MIN: BigInteger = BigDecimal(Double.MIN_VALUE.toString()).toBigInteger()
/**
* Constant value which represent edge of meaningful bits.
*
* Equals to 0.000000000001
*/
const val EPSILON = 0.000000000001
fun weakEqualDoubles(a: Double, b: Double): Boolean {
if (a == b)
return true
return (a - b).absoluteValue <= EPSILON
}
fun weakCompareDoubles(a: Double, b: Double): Int {
if (weakEqualDoubles(a, b))
return 0
if (a > b)
return 1
return -1
}
fun weakLessThan(a: Double, b: Double) = weakCompareDoubles(a, b) < 0
fun weakGreaterThan(a: Double, b: Double) = weakCompareDoubles(a, b) > 0
fun weakLessOrEqual(a: Double, b: Double) = weakCompareDoubles(a, b) <= 0
fun weakGreaterOrEqual(a: Double, b: Double) = weakCompareDoubles(a, b) >= 0
fun BigInteger.toIntSafe(): Int { fun BigInteger.toIntSafe(): Int {
if (this > BI_INT_MAX) { if (this > BI_INT_MAX) {
return Int.MAX_VALUE return Int.MAX_VALUE

View File

@ -11,46 +11,135 @@ import java.io.ObjectOutputStream
object DecimalTests { object DecimalTests {
@Test @Test
@DisplayName("ImpreciseFraction comparison") @DisplayName("Decimal comparison")
fun comparison() { fun comparison() {
check(Decimal(642, 0.43774) > Decimal(641, 0.43774)) { "must be bigger" } check(Decimal(642.43774) > Decimal(641.43774)) { "must be bigger" }
check(Decimal(642, 0.43774) > Decimal(-641, 0.43774)) { "must be bigger" } check(Decimal(642.43774) > Decimal(-641.43774)) { "must be bigger" }
check(Decimal(0) == Decimal(0)) { "integer notation" } check(Decimal(0) == Decimal(0)) { "integer notation" }
check(Decimal(0.1) > Decimal(0)) { "must be bigger" } check(Decimal(0.1) > Decimal(0)) { "must be bigger" }
check(Decimal(-0.1) < Decimal(0)) { "must be lesser" } check(Decimal(-0.1) < Decimal(0)) { "must be lesser" }
check(Decimal(-1, -0.1) < Decimal(-1)) { "must be lesser" } check(Decimal(-1.1) < Decimal(-1)) { "must be lesser" }
check(Decimal(0.1) == Decimal(0.1)) { "double notation" } check(Decimal(0.1) == Decimal(0.1)) { "double notation" }
check(Decimal("0.1") == Decimal("0.1")) { "string notation" } check(Decimal("0.1") == Decimal("0.1")) { "string notation" }
} }
@Test @Test
@DisplayName("ImpreciseFraction mathematical operations") @DisplayName("Decimal mathematical operations")
fun math() { fun math() {
check((Decimal(1) + Decimal(2)) == Decimal(3)) { "1 + 2 != 3" } assertEquals(Decimal(3), Decimal(1) + Decimal(2))
check((Decimal(0, 0.5) + Decimal(0, 0.5)) == Decimal(1)) { "0.5 + 0.5 != 1" } assertEquals(Decimal(5), Decimal(2) + Decimal(3))
check((Decimal(0, 0.5) + Decimal(0, -0.5)) == Decimal(0)) { "0.5 + -0.5 != 1" } assertEquals(Decimal(-1), Decimal(6) + Decimal(-7))
check((Decimal(0, 0.5) - Decimal(0, -0.5)) == Decimal(1)) { "0.5 - -0.5 != 1" } assertEquals(Decimal(-1), Decimal(6) - Decimal(7))
check((Decimal(0, 0.3) - Decimal(1, 0.2)) == Decimal(0, -0.9)) { "0.3 - 1.2 != -0.9 ${Decimal(0, -0.9)} ${Decimal(0, 0.3) - Decimal(1, 0.2)}" } assertEquals(Decimal(2), Decimal(1) * Decimal(2))
check((Decimal(0, 0.3) * Decimal(0, 0.3)) == Decimal(0, 0.09)) { "0.3 * 0.3 != 0.9 ${Decimal(0, 0.3) * Decimal(0, 0.3)}" } assertEquals(Decimal(4), Decimal(2) * Decimal(2))
check((Decimal(2, 0.3) * Decimal(2, 0.3)) == Decimal(5, 0.29)) { "2.3 * 2.3 != 5.29 ${Decimal(2, 0.3) * Decimal(2, 0.3)}" } assertEquals(Decimal(16), Decimal(4) * Decimal(4))
check((Decimal(4) / Decimal(2)) == Decimal(2)) { "4 / 2 != 2" } assertEquals(Decimal(100_000), Decimal(100) * Decimal(1_000))
check((Decimal(6) / Decimal(2)) == Decimal(3)) { "6 / 2 != 2" } assertEquals(Decimal(4), Decimal(16) / Decimal(4))
check((Decimal(1) / Decimal(0, 0.25)) == Decimal(4)) { "1 / 0.25 != 4" } assertEquals(Decimal(3), Decimal(12) / Decimal(4))
assertEquals(Decimal(1), Decimal(15) / Decimal(15))
assertEquals(Decimal("0.1"), Decimal(1) / Decimal(10))
assertEquals(Decimal("-0.1"), Decimal(1) / Decimal(-10))
assertEquals(Decimal("-0.1"), Decimal(-1) / Decimal(10))
assertEquals(Decimal("0.1"), Decimal(-1) / Decimal(-10))
assertEquals(Decimal("0.5"), Decimal("0.25") + Decimal("0.25"))
assertEquals(Decimal("0.5"), Decimal("0.25") * Decimal(2))
assertEquals(Decimal("-0.5"), Decimal("0.25") * Decimal(-2))
assertEquals(Decimal("-0.5"), Decimal("-0.25") * Decimal(2))
assertEquals(Decimal("0.3"), Decimal(0.2) + Decimal(0.1))
assertEquals(Decimal("0.3"), Decimal("0.2") + Decimal("0.1"))
} }
@Test @Test
@DisplayName("ImpreciseFraction store/load") @DisplayName("Decimal precision test")
fun precisionTest() {
assertEquals(0.0, Decimal(4).fractionalDouble)
assertEquals(0.2, Decimal(4.2).fractionalDouble)
assertEquals(0.2, Decimal("4.2").fractionalDouble)
assertEquals(0.25, Decimal("4.25").fractionalDouble)
assertEquals(-0.25, Decimal("-1.25").fractionalDouble)
assertEquals(-0.2, Decimal("-1.2").fractionalDouble)
assertEquals(0.0, Decimal("-1").fractionalDouble)
assertEquals(0.0f, Decimal(4).fractionalFloat)
assertEquals(0.2f, Decimal(4.2).fractionalFloat)
assertEquals(0.2f, Decimal("4.2").fractionalFloat)
assertEquals(0.25f, Decimal("4.25").fractionalFloat)
assertEquals(-0.25f, Decimal("-1.25").fractionalFloat)
assertEquals(-0.2f, Decimal("-1.2").fractionalFloat)
assertEquals(0.0f, Decimal("-1").fractionalFloat)
}
@Test
@DisplayName("Decimal toString() tests")
fun toStringTest() {
assertEquals("0.0", Decimal(0).toString())
assertEquals("0.0", Decimal("0.000").toString())
assertEquals("0.001", Decimal("0.001").toString())
assertEquals("0.1", Decimal("0.1").toString())
assertEquals("1.0", Decimal("1.0").toString())
assertEquals("4.0", Decimal("4.0").toString())
assertEquals("4.0", Decimal("4.000").toString())
assertEquals("4.0001", Decimal("4.0001").toString())
assertEquals("-0.001", Decimal("-0.001").toString())
assertEquals("-0.1", Decimal("-0.1").toString())
assertEquals("-1.0", Decimal("-1.0").toString())
assertEquals("-4.0", Decimal("-4.0").toString())
assertEquals("-4.0", Decimal("-4.000").toString())
assertEquals("-4.0001", Decimal("-4.0001").toString())
assertEquals("0.0", Decimal("0.000").toString(1))
assertEquals("0.00", Decimal("0.000").toString(2))
assertEquals("0.000", Decimal("0.000").toString(3))
assertEquals("0.001", Decimal("0.001").toString(3))
assertEquals("0.00", Decimal("0.001").toString(2))
assertEquals("0.0", Decimal("0.001").toString(1))
assertEquals("1.0", Decimal("1.001").toString(1))
assertEquals("1.1", Decimal("1.101").toString(1))
assertEquals("1.10", Decimal("1.101").toString(2))
assertEquals("1.101", Decimal("1.101").toString(3))
assertEquals("1.1010", Decimal("1.101").toString(4))
}
@Test
@DisplayName("Decimal store/load")
fun storeLoad() { fun storeLoad() {
run { run {
val f = Decimal(4, 0.28) val f = Decimal(4.28)
val loaded = Decimal.fromByteArray(f.toByteArray()) val loaded = Decimal.fromByteArray(f.toByteArray())
check(f == loaded) { "$f != $loaded" } assertEquals(f, loaded)
} }
run { run {
val f = Decimal(32748293658335L, 0.3472302174) val f = Decimal(1)
val loaded = Decimal.fromByteArray(f.toByteArray()) val loaded = Decimal.fromByteArray(f.toByteArray())
check(f == loaded) { "$f != $loaded" } assertEquals(f, loaded)
}
run {
val f = Decimal(0)
val loaded = Decimal.fromByteArray(f.toByteArray())
assertEquals(f, loaded)
}
run {
val f = Decimal("23784399123654793927324950293475093257324097021387409873240957021934750934089734095273098475")
val loaded = Decimal.fromByteArray(f.toByteArray())
assertEquals(f, loaded)
}
run {
val f = Decimal("23784399123654793927324950293475093257324097021387409873240957021934750934089734095273098475.25")
val loaded = Decimal.fromByteArray(f.toByteArray())
assertEquals(f, loaded)
}
run {
val f = Decimal(32748293658335.3472302174)
val loaded = Decimal.fromByteArray(f.toByteArray())
assertEquals(f, loaded)
} }
} }
@ -67,7 +156,7 @@ object DecimalTests {
} }
@Test @Test
@DisplayName("ImpreciseFraction serialization") @DisplayName("Decimal serialization")
fun serialization() { fun serialization() {
val output = FastByteArrayOutputStream() val output = FastByteArrayOutputStream()
val outputStream = ObjectOutputStream(output) val outputStream = ObjectOutputStream(output)