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 package ru.dbotthepony.mc.otm.block.entity
import net.minecraft.MethodsReturnNonnullByDefault
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.network.chat.Component import net.minecraft.network.chat.Component
import net.minecraft.network.chat.TranslatableComponent 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.capability.MatteryMachineEnergyStorage
import ru.dbotthepony.mc.otm.core.Fraction import ru.dbotthepony.mc.otm.core.Fraction
import ru.dbotthepony.mc.otm.menu.AndroidStationMenu import ru.dbotthepony.mc.otm.menu.AndroidStationMenu
import javax.annotation.ParametersAreNonnullByDefault
class BlockEntityAndroidStation(p_155229_: BlockPos, p_155230_: BlockState) : class BlockEntityAndroidStation(p_155229_: BlockPos, p_155230_: BlockState) :
BlockEntityMatteryPowered(Registry.BlockEntities.ANDROID_STATION, p_155229_, p_155230_), MenuProvider { BlockEntityMatteryPowered(Registry.BlockEntities.ANDROID_STATION, p_155229_, p_155230_), MenuProvider {

View File

@ -1,16 +1,12 @@
package ru.dbotthepony.mc.otm.block.entity.worker 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.world.level.block.entity.BlockEntityType
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.block.entity.BlockEntityMatteryPowered import ru.dbotthepony.mc.otm.block.entity.BlockEntityMatteryPowered
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.DoubleTag import net.minecraft.nbt.DoubleTag
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block 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.core.Fraction
import ru.dbotthepony.mc.otm.ifHas import ru.dbotthepony.mc.otm.ifHas
import ru.dbotthepony.mc.otm.set 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.IStorageTuple
import ru.dbotthepony.mc.otm.storage.ItemStackWrapper import ru.dbotthepony.mc.otm.storage.ItemStackWrapper
import ru.dbotthepony.mc.otm.storage.StorageObjectRegistry import ru.dbotthepony.mc.otm.storage.StorageObjectRegistry
import ru.dbotthepony.mc.otm.storage.StorageObjectTuple
import java.util.* import java.util.*
class ItemMatteryDrive : AbstractMatteryDrive<ItemStackWrapper>, IItemMatteryDrive { class ItemMatteryDrive : AbstractMatteryDrive<ItemStackWrapper>, IItemMatteryDrive {

View File

@ -10,7 +10,7 @@ import java.math.BigInteger
import java.math.MathContext import java.math.MathContext
import java.math.RoundingMode import java.math.RoundingMode
fun powScale(int: Int): BigInteger { private fun powScale(int: Int): BigInteger {
if (int <= 0) if (int <= 0)
return BigInteger.ONE return BigInteger.ONE
@ -22,7 +22,7 @@ fun powScale(int: Int): BigInteger {
return result return result
} }
fun powUnscaled(unscaled: BigInteger, scale: Int): BigInteger { private fun powUnscaled(unscaled: BigInteger, scale: Int): BigInteger {
if (scale >= 0) if (scale >= 0)
return unscaled return unscaled
@ -37,7 +37,7 @@ fun powUnscaled(unscaled: BigInteger, scale: Int): BigInteger {
val DEFAULT_MATH_CONTEXT = MathContext(64, RoundingMode.HALF_UP) val DEFAULT_MATH_CONTEXT = MathContext(64, RoundingMode.HALF_UP)
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
inline fun invertCompare(int: Int): Int { private inline fun invertCompare(int: Int): Int {
if (int == 0) if (int == 0)
return 0 return 0
@ -206,7 +206,7 @@ private fun compactTwo(value1: BigInteger, value2: BigInteger, compact: Boolean
return compactTwoMod(value1, value2, compact) return compactTwoMod(value1, value2, compact)
} }
fun unsignedInt(value: Byte): Int { private fun unsignedInt(value: Byte): Int {
if (value < 0) { if (value < 0) {
return 256 + value 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.core.Fraction
import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.data.FractionDataContainer 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") @Suppress("unused")
class LevelGaugeWidget(menu: MatteryMenu) : AbstractWidget(menu) { 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.capability.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.core.Fraction import ru.dbotthepony.mc.otm.core.Fraction
import java.util.* 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>) { 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 { 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.Test
import org.junit.jupiter.api.assertThrows import org.junit.jupiter.api.assertThrows
import ru.dbotthepony.mc.otm.core.Fraction 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.BigDecimal
import java.math.BigInteger import java.math.BigInteger
import kotlin.random.Random
object FractionTests { object FractionTests {
@Test @Test
@ -138,6 +137,15 @@ object FractionTests {
Fraction(1043, -648), 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( private val samples2 = arrayOf(
(9475.0 / 4729), (9475.0 / 4729),
(23535.0 / 58723), (23535.0 / 58723),
@ -161,6 +169,7 @@ object FractionTests {
fun performance() { fun performance() {
val rand = java.util.Random() val rand = java.util.Random()
val blackHole = arrayOfNulls<Fraction>(100_000) val blackHole = arrayOfNulls<Fraction>(100_000)
val blackHoleIM = arrayOfNulls<ImpreciseFraction>(100_000)
val size = samples.size val size = samples.size
var time = System.currentTimeMillis() 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") 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 var sum = Fraction.ZERO
// перемешаем чтоб оптимизатор не отбросил // перемешаем чтоб оптимизатор не отбросил
@ -227,6 +249,14 @@ object FractionTests {
blackHole3[i] = blackHole3[rand.nextInt(size)] 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") 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" }
}
}