ImpreciseFraction

This commit is contained in:
DBotThePony 2022-01-25 21:35:48 +07:00
parent 6cd04682d7
commit dab5cd6a2b
Signed by: DBot
GPG Key ID: DCC23B5715498507
10 changed files with 401 additions and 20 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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<ItemStackWrapper>, IItemMatteryDrive {

View File

@ -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
}

View File

@ -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<ImpreciseFraction> {
@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
}
}
}

View File

@ -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) {

View File

@ -1,4 +0,0 @@
package ru.dbotthepony.mc.otm.storage
import ru.dbotthepony.mc.otm.core.Fraction
import java.util.*

View File

@ -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<T : IStorageStack>(val obj: T, val remote_id: UUID, val provider: IStorageView<T>, val local: LocalTuple<T>) {
fun extract(amount: Fraction, simulate: Boolean): T {

View File

@ -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<Fraction>(100_000)
val blackHoleIM = arrayOfNulls<ImpreciseFraction>(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")
}

View File

@ -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" }
}
}