From dab5cd6a2b72db34f916068102e687333befe871 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Tue, 25 Jan 2022 21:35:48 +0700 Subject: [PATCH] ImpreciseFraction --- .../block/entity/BlockEntityAndroidStation.kt | 2 - .../entity/worker/BlockEntityMatteryWorker.kt | 4 - .../otm/capability/drive/ItemMatteryDrive.kt | 1 - .../ru/dbotthepony/mc/otm/core/Fraction.kt | 8 +- .../mc/otm/core/ImpreciseFraction.kt | 322 ++++++++++++++++++ .../mc/otm/menu/widget/LevelGaugeWidget.kt | 2 - .../mc/otm/storage/IStorageView.kt | 4 - .../mc/otm/storage/VirtualComponent.kt | 1 - .../dbotthepony/mc/otm/tests/FractionTests.kt | 34 +- .../mc/otm/tests/ImpreciseFractionTests.kt | 43 +++ 10 files changed, 401 insertions(+), 20 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/core/ImpreciseFraction.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/storage/IStorageView.kt create mode 100644 src/test/kotlin/ru/dbotthepony/mc/otm/tests/ImpreciseFractionTests.kt diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/BlockEntityAndroidStation.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/BlockEntityAndroidStation.kt index 29b7de3c2..16c0994fe 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/BlockEntityAndroidStation.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/BlockEntityAndroidStation.kt @@ -1,6 +1,5 @@ package ru.dbotthepony.mc.otm.block.entity -import net.minecraft.MethodsReturnNonnullByDefault import net.minecraft.core.BlockPos import net.minecraft.network.chat.Component import net.minecraft.network.chat.TranslatableComponent @@ -16,7 +15,6 @@ import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryMachineEnergyStorage import ru.dbotthepony.mc.otm.core.Fraction import ru.dbotthepony.mc.otm.menu.AndroidStationMenu -import javax.annotation.ParametersAreNonnullByDefault class BlockEntityAndroidStation(p_155229_: BlockPos, p_155230_: BlockState) : BlockEntityMatteryPowered(Registry.BlockEntities.ANDROID_STATION, p_155229_, p_155230_), MenuProvider { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/worker/BlockEntityMatteryWorker.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/worker/BlockEntityMatteryWorker.kt index dbe2d9990..94dcd6de8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/worker/BlockEntityMatteryWorker.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/worker/BlockEntityMatteryWorker.kt @@ -1,16 +1,12 @@ package ru.dbotthepony.mc.otm.block.entity.worker -import net.minecraft.MethodsReturnNonnullByDefault -import javax.annotation.ParametersAreNonnullByDefault import net.minecraft.world.level.block.entity.BlockEntityType import net.minecraft.core.BlockPos import net.minecraft.world.level.block.state.BlockState import ru.dbotthepony.mc.otm.block.entity.BlockEntityMatteryPowered import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.DoubleTag -import net.minecraft.world.level.Level import net.minecraft.world.level.block.Block -import net.minecraft.world.level.block.entity.BlockEntity import ru.dbotthepony.mc.otm.core.Fraction import ru.dbotthepony.mc.otm.ifHas import ru.dbotthepony.mc.otm.set diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt index f070fc40a..3c3d1e489 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/drive/ItemMatteryDrive.kt @@ -11,7 +11,6 @@ import ru.dbotthepony.mc.otm.set import ru.dbotthepony.mc.otm.storage.IStorageTuple import ru.dbotthepony.mc.otm.storage.ItemStackWrapper import ru.dbotthepony.mc.otm.storage.StorageObjectRegistry -import ru.dbotthepony.mc.otm.storage.StorageObjectTuple import java.util.* class ItemMatteryDrive : AbstractMatteryDrive, IItemMatteryDrive { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Fraction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Fraction.kt index 5148c3b40..33431f108 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Fraction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Fraction.kt @@ -10,7 +10,7 @@ import java.math.BigInteger import java.math.MathContext import java.math.RoundingMode -fun powScale(int: Int): BigInteger { +private fun powScale(int: Int): BigInteger { if (int <= 0) return BigInteger.ONE @@ -22,7 +22,7 @@ fun powScale(int: Int): BigInteger { return result } -fun powUnscaled(unscaled: BigInteger, scale: Int): BigInteger { +private fun powUnscaled(unscaled: BigInteger, scale: Int): BigInteger { if (scale >= 0) return unscaled @@ -37,7 +37,7 @@ fun powUnscaled(unscaled: BigInteger, scale: Int): BigInteger { val DEFAULT_MATH_CONTEXT = MathContext(64, RoundingMode.HALF_UP) @Suppress("NOTHING_TO_INLINE") -inline fun invertCompare(int: Int): Int { +private inline fun invertCompare(int: Int): Int { if (int == 0) return 0 @@ -206,7 +206,7 @@ private fun compactTwo(value1: BigInteger, value2: BigInteger, compact: Boolean return compactTwoMod(value1, value2, compact) } -fun unsignedInt(value: Byte): Int { +private fun unsignedInt(value: Byte): Int { if (value < 0) { return 256 + value } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/ImpreciseFraction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ImpreciseFraction.kt new file mode 100644 index 000000000..264ea73fa --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/ImpreciseFraction.kt @@ -0,0 +1,322 @@ +package ru.dbotthepony.mc.otm.core + +import net.minecraft.nbt.ByteArrayTag +import net.minecraft.nbt.StringTag +import net.minecraft.nbt.Tag +import java.math.BigDecimal +import java.math.BigInteger +import java.math.MathContext +import java.nio.ByteBuffer +import kotlin.math.absoluteValue + +private fun isZero(value: BigInteger) = value == BigInteger.ZERO + +private val LONG_BUFF = ByteBuffer.allocate(8) + +private fun longToBytes(value: Long): ByteArray { + LONG_BUFF.position(0) + LONG_BUFF.putLong(value) + return LONG_BUFF.array().copyOf() +} + +private fun bytesToLong(value: ByteArray, from: Int = 0): Long { + val arr = LONG_BUFF.array() + arr[0] = value[from] + arr[1] = value[from + 1] + arr[2] = value[from + 2] + arr[3] = value[from + 3] + arr[4] = value[from + 4] + arr[5] = value[from + 5] + arr[6] = value[from + 6] + arr[7] = value[from + 7] + LONG_BUFF.position(0) + return LONG_BUFF.long +} + +const val EPSILON = 0.0000001 + +private fun cmpDouble(a: Double, b: Double): Boolean { + if (a == b) + return true + + return (a - b).absoluteValue <= EPSILON +} + +@Suppress("unused") +class ImpreciseFraction @JvmOverloads constructor(whole: BigInteger, decimal: Double = 0.0) : Comparable { + @JvmOverloads constructor(whole: Byte, decimal: Double = 0.0) : this(BigInteger.valueOf(whole.toLong()), decimal) + @JvmOverloads constructor(whole: Short, decimal: Double = 0.0) : this(BigInteger.valueOf(whole.toLong()), decimal) + @JvmOverloads constructor(whole: Int, decimal: Double = 0.0) : this(BigInteger.valueOf(whole.toLong()), decimal) + @JvmOverloads constructor(whole: Long, decimal: Double = 0.0) : this(BigInteger.valueOf(whole), decimal) + constructor(value: BigDecimal) : this(value.toBigInteger(), value.remainder(BigDecimal.ONE).toDouble()) // TODO + constructor(value: Float) : this(BigDecimal(value.toString())) + constructor(value: Double) : this(BigDecimal(value.toString())) + constructor(value: String) : this(BigDecimal(value)) + + val decimal: Double + val whole: BigInteger + + init { + var decimal = decimal + var whole = whole + + if (decimal <= -1f || decimal >= 1f) { + val frac = decimal % 1f + whole += BigInteger.valueOf((decimal - frac).toLong()) + decimal = frac + } + + // дробная часть равна нулю + if (decimal == -0.0 || decimal == 0.0) { + if (!isZero(whole)) { + this.decimal = 0.0 + this.whole = whole + } else { // сохраняет минус ноль + this.decimal = decimal + this.whole = whole + } + // целая часть и дробная часть имеют одинаковые знаки + } else if (decimal > 0.0 && whole.signum() >= 0 || decimal < 0.0 && whole.signum() <= 0) { + this.decimal = decimal + this.whole = whole + } else { + // целая часть и дробная часть имеют разные знаки + + if (decimal > 0.0) { + this.decimal = decimal - 1f + this.whole = whole + BigInteger.ONE + //} else if (double < 0.0 && whole.signum() > 0) { + } else { + this.decimal = decimal + 1f + this.whole = whole - BigInteger.ONE + } + + //throw InternalError("This should never happen, but it happened. Input was: $whole $double") + } + } + + fun isNaN() = decimal.isNaN() + + operator fun plus(other: ImpreciseFraction) = ImpreciseFraction(whole + other.whole, decimal + other.decimal) + operator fun minus(other: ImpreciseFraction) = ImpreciseFraction(whole - other.whole, decimal - other.decimal) + + operator fun times(other: ImpreciseFraction): ImpreciseFraction { + val a = whole + val c = other.whole + + val b = 1.0 / decimal + val d = 1.0 / other.decimal + + val bL = b.toLong() + val dL = d.toLong() + + val bc = c.divideAndRemainder(BigInteger.valueOf(bL)) + val ad = a.divideAndRemainder(BigInteger.valueOf(dL)) + + return ImpreciseFraction( + a * c + bc[0] + ad[0], + decimal * other.decimal + bc[1].toDouble() / b + ad[1].toDouble() / d + ) + } + + operator fun div(other: ImpreciseFraction): ImpreciseFraction { + val a = toBigDecmial() + val b = other.toBigDecmial() + val div = a.divideAndRemainder(b, MathContext(a.precision() + b.precision())) + + val bD = b.toDouble() + + if (bD.isInfinite()) { + return ImpreciseFraction(div[0].toBigInteger()) + } + + return ImpreciseFraction(div[0].toBigInteger(), div[1].toDouble() / bD) + } + + operator fun unaryMinus(): ImpreciseFraction { + return ImpreciseFraction(-whole, decimal) + } + + override fun equals(other: Any?): Boolean { + if (isNaN()) + return false + + if (other is ImpreciseFraction) { + return other.whole == whole && cmpDouble(decimal, other.decimal) + } + + return super.equals(other) + } + + fun equalsStrict(other: Any?): Boolean { + if (isNaN()) + return false + + if (other is ImpreciseFraction) { + return other.whole == whole && other.decimal == decimal + } + + return super.equals(other) + } + + override fun hashCode(): Int { + if (isNaN()) + return Double.NaN.hashCode() + + return 31 * decimal.hashCode() + whole.hashCode() + } + + override fun toString(): String { + if (isNaN()) + return "NaN" + + return when (signum()) { + 1 -> "$whole.${decimal.toString().substring(2)}" + 0 -> "0.0" + -1 -> "$whole.${decimal.toString().substring(3)}" + else -> throw IllegalArgumentException("invalid signum") + } + } + + fun toDouble(): Double { + return java.lang.Double.parseDouble(toString()) + } + + fun toBigDecmial(): BigDecimal { + return BigDecimal(whole) + BigDecimal(decimal) + } + + fun signum(): Int { + val sign = whole.signum() + + if (sign != 0) { + return sign + } + + return if (decimal > 0.0) 1 else if (decimal < 0.0) -1 else 0 + } + + override fun compareTo(other: ImpreciseFraction): Int { + if (isNaN()) + return 1 + else if (other.isNaN()) + return -1 + + val a = signum() + val b = other.signum() + + if (a < b) + return -1 + else if (a > b) + return 1 + + if (other.whole == whole) { + if (cmpDouble(decimal, other.decimal)) { + return 0 + } + + return if (other.decimal < decimal) 1 else -1 + } + + return whole.compareTo(other.whole) + } + + fun compareToStrict(other: ImpreciseFraction): Int { + if (isNaN()) + return 1 + else if (other.isNaN()) + return -1 + + val a = signum() + val b = other.signum() + + if (a < b) + return -1 + else if (a > b) + return 1 + + if (other.whole == whole) { + if (other.decimal == decimal) { + return 0 + } + + return if (other.decimal < decimal) 1 else -1 + } + + return whole.compareTo(other.whole) + } + + fun toBytesArray(): ByteArray { + val whole = whole.toByteArray() + return byteArrayOf(whole.size.toByte(), *whole, *longToBytes(decimal.toBits())) + } + + fun serializeNBT(): Tag { + return ByteArrayTag(toBytesArray()) + } + + fun isZero() = cmpDouble(decimal, 0.0) && isZero(whole) + + fun moreThanZero(): ImpreciseFraction { + if (signum() >= 0) + return this + + return ZERO + } + + fun lessOrZero(): ImpreciseFraction { + if (signum() <= 0) + return this + + return ZERO + } + + fun max(vararg others: ImpreciseFraction): ImpreciseFraction { + var max = this + + for (other in others) { + if (max < other) { + max = other + } + } + + return max + } + + fun min(vararg others: ImpreciseFraction): ImpreciseFraction { + var min = this + + for (other in others) { + if (min > other) { + min = other + } + } + + return min + } + + companion object { + @JvmField val MINUS_ZERO = ImpreciseFraction(0, -0.0) + @JvmField val ZERO = ImpreciseFraction(BigInteger.ZERO) + @JvmField val ONE = ImpreciseFraction(BigInteger.ONE) + @JvmField val TWO = ImpreciseFraction(BigInteger.TWO) + @JvmField val TEN = ImpreciseFraction(BigInteger.TEN) + + fun fromBytesArray(input: ByteArray): ImpreciseFraction { + val size = if (input[0] < 0) input[0].toInt() + 256 else input[0].toInt() + val slice = input.copyOfRange(1, 1 + size) + val bits = bytesToLong(input, 1 + size) + return ImpreciseFraction(BigInteger(slice), Double.fromBits(bits)) + } + + fun deserializeNBT(input: Tag?): ImpreciseFraction { + if (input is ByteArrayTag) { + return fromBytesArray(input.asByteArray) + } else if (input is StringTag) { + return ImpreciseFraction(input.asString) + } + + return ZERO + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt index dabc99612..469057044 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/widget/LevelGaugeWidget.kt @@ -6,8 +6,6 @@ import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage import ru.dbotthepony.mc.otm.core.Fraction import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.data.FractionDataContainer -import ru.dbotthepony.mc.otm.menu.data.IntDataContainer -import ru.dbotthepony.mc.otm.menu.data.ShortDataContainer @Suppress("unused") class LevelGaugeWidget(menu: MatteryMenu) : AbstractWidget(menu) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/IStorageView.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/IStorageView.kt deleted file mode 100644 index e64453201..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/IStorageView.kt +++ /dev/null @@ -1,4 +0,0 @@ -package ru.dbotthepony.mc.otm.storage - -import ru.dbotthepony.mc.otm.core.Fraction -import java.util.* diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt index 16d64f31c..302a525ad 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/storage/VirtualComponent.kt @@ -3,7 +3,6 @@ package ru.dbotthepony.mc.otm.storage import ru.dbotthepony.mc.otm.capability.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.core.Fraction import java.util.* -import java.util.function.Supplier class RemoteTuple(val obj: T, val remote_id: UUID, val provider: IStorageView, val local: LocalTuple) { fun extract(amount: Fraction, simulate: Boolean): T { diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FractionTests.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FractionTests.kt index f20e1bb91..9a2afa555 100644 --- a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FractionTests.kt +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/FractionTests.kt @@ -4,10 +4,9 @@ import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import ru.dbotthepony.mc.otm.core.Fraction -import java.lang.Math.floor +import ru.dbotthepony.mc.otm.core.ImpreciseFraction import java.math.BigDecimal import java.math.BigInteger -import kotlin.random.Random object FractionTests { @Test @@ -138,6 +137,15 @@ object FractionTests { Fraction(1043, -648), ) + private val samplesIM = arrayOf( + ImpreciseFraction(9475.0 / 4729), + ImpreciseFraction(23535.0 / 58723), + ImpreciseFraction(-4852.0 / 6859), + ImpreciseFraction(-45623.0 / -76849), + ImpreciseFraction(38494.0 / -76849), + ImpreciseFraction(1043.0 / -648), + ) + private val samples2 = arrayOf( (9475.0 / 4729), (23535.0 / 58723), @@ -161,6 +169,7 @@ object FractionTests { fun performance() { val rand = java.util.Random() val blackHole = arrayOfNulls(100_000) + val blackHoleIM = arrayOfNulls(100_000) val size = samples.size var time = System.currentTimeMillis() @@ -175,6 +184,19 @@ object FractionTests { println("Mean time for Fraction operation is ~${System.currentTimeMillis() - time} ms per 100,000 ops") + time = System.currentTimeMillis() + + for (i in 0 until 100_000) { + when (rand.nextInt(3)) { + 0 -> blackHoleIM[i] = samplesIM[rand.nextInt(size)] + samplesIM[rand.nextInt(size)] + 1 -> blackHoleIM[i] = samplesIM[rand.nextInt(size)] - samplesIM[rand.nextInt(size)] + 2 -> blackHoleIM[i] = samplesIM[rand.nextInt(size)] * samplesIM[rand.nextInt(size)] + // 3 -> blackHole[i] = samples[rand.nextInt(size)] / samples[rand.nextInt(size)] + } + } + + println("Mean time for ImpreciseFraction operation is ~${System.currentTimeMillis() - time} ms per 100,000 ops") + var sum = Fraction.ZERO // перемешаем чтоб оптимизатор не отбросил @@ -227,6 +249,14 @@ object FractionTests { blackHole3[i] = blackHole3[rand.nextInt(size)] } + var sum4 = 0.0 + + // перемешаем чтоб оптимизатор не отбросил + for (i in 0 until size) { + sum4 += blackHoleIM[i]!!.toDouble() + blackHoleIM[i] = blackHoleIM[rand.nextInt(size)] + } + println("$sum $sum2 $sum3") } diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ImpreciseFractionTests.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ImpreciseFractionTests.kt new file mode 100644 index 000000000..278da8ec6 --- /dev/null +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/ImpreciseFractionTests.kt @@ -0,0 +1,43 @@ +package ru.dbotthepony.mc.otm.tests + +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import ru.dbotthepony.mc.otm.core.ImpreciseFraction + +object ImpreciseFractionTests { + @Test + @DisplayName("ImpreciseFraction comparison") + fun comparison() { + check(ImpreciseFraction(642, 0.43774) > ImpreciseFraction(641, 0.43774)) { "must be bigger" } + check(ImpreciseFraction(642, 0.43774) > ImpreciseFraction(-641, 0.43774)) { "must be bigger" } + check(ImpreciseFraction(0) == ImpreciseFraction(0)) { "integer notation" } + check(ImpreciseFraction(0.1) > ImpreciseFraction(0)) { "must be bigger" } + check(ImpreciseFraction(-0.1) < ImpreciseFraction(0)) { "must be lesser" } + check(ImpreciseFraction(-1, -0.1) < ImpreciseFraction(-1)) { "must be lesser" } + check(ImpreciseFraction(0.1) == ImpreciseFraction(0.1)) { "double notation" } + check(ImpreciseFraction("0.1") == ImpreciseFraction("0.1")) { "string notation" } + } + + @Test + @DisplayName("ImpreciseFraction mathematical operations") + fun math() { + check((ImpreciseFraction(1) + ImpreciseFraction(2)) == ImpreciseFraction(3)) { "1 + 2 != 3" } + check((ImpreciseFraction(0, 0.5) + ImpreciseFraction(0, 0.5)) == ImpreciseFraction(1)) { "0.5 + 0.5 != 1" } + check((ImpreciseFraction(0, 0.5) + ImpreciseFraction(0, -0.5)) == ImpreciseFraction(0)) { "0.5 + -0.5 != 1" } + check((ImpreciseFraction(0, 0.5) - ImpreciseFraction(0, -0.5)) == ImpreciseFraction(1)) { "0.5 - -0.5 != 1" } + check((ImpreciseFraction(0, 0.3) - ImpreciseFraction(1, 0.2)) == ImpreciseFraction(0, -0.9)) { "0.3 - 1.2 != -0.9" } + check((ImpreciseFraction(0, 0.3) * ImpreciseFraction(0, 0.3)) == ImpreciseFraction(0, 0.09)) { "0.3 * 0.3 != 0.9 ${ImpreciseFraction(0, 0.3) * ImpreciseFraction(0, 0.3)}" } + check((ImpreciseFraction(2, 0.3) * ImpreciseFraction(2, 0.3)) == ImpreciseFraction(5, 0.29)) { "2.3 * 2.3 != 5.29 ${ImpreciseFraction(2, 0.3) * ImpreciseFraction(2, 0.3)}" } + check((ImpreciseFraction(4) / ImpreciseFraction(2)) == ImpreciseFraction(2)) { "4 / 2 != 2" } + check((ImpreciseFraction(6) / ImpreciseFraction(2)) == ImpreciseFraction(3)) { "6 / 2 != 2" } + check((ImpreciseFraction(1) / ImpreciseFraction(0, 0.25)) == ImpreciseFraction(4)) { "1 / 0.25 != 4" } + } + + @Test + @DisplayName("ImpreciseFraction store/load") + fun storeLoad() { + val f = ImpreciseFraction(4, 0.28) + val loaded = ImpreciseFraction.fromBytesArray(f.toBytesArray()) + check(f == loaded) { "$f != $loaded" } + } +}