diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/RandomUtils.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/RandomUtils.kt index 62214303e..38dd8b17e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/RandomUtils.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/RandomUtils.kt @@ -4,9 +4,10 @@ import it.unimi.dsi.fastutil.ints.IntList import net.minecraft.Util import net.minecraft.util.RandomSource import ru.dbotthepony.mc.otm.core.math.Decimal +import java.math.BigInteger import java.util.* import java.util.random.RandomGenerator -import kotlin.NoSuchElementException +import kotlin.experimental.and import kotlin.math.ln import kotlin.math.sqrt @@ -103,14 +104,116 @@ fun List.random(random: RandomSource): T { return get(random.nextInt(size)) } -fun RandomSource.nextDecimal(min: Decimal, max: Decimal, round: Boolean = false): Decimal { - val value = nextDouble() +class RandomByteSource(private val source: RandomSource) { + private val bytes = ByteArray(8) + private var i = 7 - return if (round) { - Decimal((min + (max - min) * value).whole) - } else { - min + (max - min) * value + fun next(): Byte { + if (++i == 8) { + i = 0 + + var generate = source.nextLong() + + for (i in 0 .. 7) { + bytes[i] = generate.toByte() + generate = generate ushr 8 + } + } + + return bytes[i] } + + fun next(bound: Int): Byte { + require(bound > 0) { "Bound must be positive" } + val m = bound - 1 + var r = next().toInt().and(0xFF) + + if (bound and m == 0) { + r = r and m + } else { + var u = r ushr 1 + + while (true) { + r = u % bound + + if (u + m - r < 0) { + u = next().toInt().and(0xFF).ushr(1) + } else { + break + } + } + } + + return r.toByte() + } + + fun next(bytes: ByteArray) { + for (i in bytes.indices) { + bytes[i] = next() + } + } +} + +/** + * Uniformely distributed [Decimal] value on [0,[bound]) range + * + * If [round] is `true`, will only return integers + */ +fun RandomSource.nextDecimal(bound: Decimal, round: Boolean = false): Decimal { + if (round) + require(bound > Decimal.ZERO) { "Bound must be positive, $bound given" } + else + require(bound >= Decimal.ONE) { "Bound must be 1 or bigger, $bound given" } + + require(bound.isFinite) { "Bound must be finite" } + + val bytes = RandomByteSource(this) + + if (round) { + val thisBytes = bound.whole.toByteArray() + val generateBytes = ByteArray(thisBytes.size) + bytes.next(generateBytes) + + generateBytes[0] = generateBytes[0].and(127) + + if (generateBytes[0] >= thisBytes[0]) { + generateBytes[0] = bytes.next(thisBytes[0].toInt().and(127)) + } + + return Decimal(BigInteger(generateBytes)) + } else { + val thisBytes = bound.mag.toByteArray() + val generateBytes = ByteArray(thisBytes.size) + bytes.next(generateBytes) + + generateBytes[0] = generateBytes[0].and(127) + + if (generateBytes[0] >= thisBytes[0]) { + generateBytes[0] = bytes.next(thisBytes[0].toInt().and(127)) + } + + return Decimal.raw(BigInteger(generateBytes)) + } +} + +/** + * Uniformely distributed [Decimal] value on [[origin],[bound]) range + * + * If [round] is `true`, will only return integers + */ +fun RandomSource.nextDecimal(origin: Decimal, bound: Decimal, round: Boolean = false): Decimal { + require(origin < bound) { "Origin must be less than bound: $origin < $bound" } + require(origin.isFinite) { "Origin must be finite" } + require(bound.isFinite) { "Bound must be finite" } + + return origin + nextDecimal(bound - origin, round) +} + +/** + * Uniformely distributed [Decimal] value on [0,1) range + */ +fun RandomSource.nextDecimal(): Decimal { + return nextDecimal(Decimal.ZERO, Decimal.ONE) } fun RandomSource.nextVariance(value: Decimal, round: Boolean = false): Decimal { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt index 0a85c870a..284cb53be 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt @@ -39,6 +39,8 @@ sealed class Decimal : Number(), Comparable { */ abstract val fractional: BigInteger + abstract internal val mag: BigInteger + /** * *Signed* normalized (-1,1) fractional part of this Decimal, as [Float] */ @@ -181,7 +183,7 @@ sealed class Decimal : Number(), Comparable { return toBigDecmial().divide(divisor.toBigDecmial(), PERCENTAGE_CONTEXT).toFloat() } - private class Regular(val mag: BigInteger, marker: Nothing?) : Decimal() { + private class Regular(override val mag: BigInteger, marker: Nothing?) : Decimal() { constructor(value: BigInteger) : this(value * PRECISION_POW_BI, null) constructor(value: BigDecimal) : this(value.setScale(PRECISION, RoundingMode.HALF_UP).unscaledValue(), null) constructor(value: Float) : this(BigDecimal.valueOf(value.toDouble())) @@ -703,6 +705,9 @@ sealed class Decimal : Number(), Comparable { } private object PositiveInfinity : Decimal() { + override val mag: BigInteger + get() = throw UnsupportedOperationException() + private fun readResolve(): Any = PositiveInfinity override val isInfinite: Boolean @@ -952,6 +957,9 @@ sealed class Decimal : Number(), Comparable { } private object NegativeInfinity : Decimal() { + override val mag: BigInteger + get() = throw UnsupportedOperationException() + private fun readResolve(): Any = NegativeInfinity override val isInfinite: Boolean @@ -1195,6 +1203,9 @@ sealed class Decimal : Number(), Comparable { } private object Zero : Decimal() { + override val mag: BigInteger + get() = BigInteger.ZERO + private fun readResolve(): Any = Zero override val isInfinite: Boolean