And now, proper random decimals implementation

This commit is contained in:
DBotThePony 2025-03-26 22:06:39 +07:00
parent c965bcdc82
commit 1087e755ff
Signed by: DBot
GPG Key ID: DCC23B5715498507
2 changed files with 122 additions and 8 deletions

View File

@ -4,9 +4,10 @@ import it.unimi.dsi.fastutil.ints.IntList
import net.minecraft.Util import net.minecraft.Util
import net.minecraft.util.RandomSource import net.minecraft.util.RandomSource
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import java.math.BigInteger
import java.util.* import java.util.*
import java.util.random.RandomGenerator import java.util.random.RandomGenerator
import kotlin.NoSuchElementException import kotlin.experimental.and
import kotlin.math.ln import kotlin.math.ln
import kotlin.math.sqrt import kotlin.math.sqrt
@ -103,14 +104,116 @@ fun <T> List<T>.random(random: RandomSource): T {
return get(random.nextInt(size)) return get(random.nextInt(size))
} }
fun RandomSource.nextDecimal(min: Decimal, max: Decimal, round: Boolean = false): Decimal { class RandomByteSource(private val source: RandomSource) {
val value = nextDouble() private val bytes = ByteArray(8)
private var i = 7
return if (round) { fun next(): Byte {
Decimal((min + (max - min) * value).whole) if (++i == 8) {
} else { i = 0
min + (max - min) * value
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 { fun RandomSource.nextVariance(value: Decimal, round: Boolean = false): Decimal {

View File

@ -39,6 +39,8 @@ sealed class Decimal : Number(), Comparable<Decimal> {
*/ */
abstract val fractional: BigInteger abstract val fractional: BigInteger
abstract internal val mag: BigInteger
/** /**
* *Signed* normalized (-1,1) fractional part of this Decimal, as [Float] * *Signed* normalized (-1,1) fractional part of this Decimal, as [Float]
*/ */
@ -181,7 +183,7 @@ sealed class Decimal : Number(), Comparable<Decimal> {
return toBigDecmial().divide(divisor.toBigDecmial(), PERCENTAGE_CONTEXT).toFloat() 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: BigInteger) : this(value * PRECISION_POW_BI, null)
constructor(value: BigDecimal) : this(value.setScale(PRECISION, RoundingMode.HALF_UP).unscaledValue(), null) constructor(value: BigDecimal) : this(value.setScale(PRECISION, RoundingMode.HALF_UP).unscaledValue(), null)
constructor(value: Float) : this(BigDecimal.valueOf(value.toDouble())) constructor(value: Float) : this(BigDecimal.valueOf(value.toDouble()))
@ -703,6 +705,9 @@ sealed class Decimal : Number(), Comparable<Decimal> {
} }
private object PositiveInfinity : Decimal() { private object PositiveInfinity : Decimal() {
override val mag: BigInteger
get() = throw UnsupportedOperationException()
private fun readResolve(): Any = PositiveInfinity private fun readResolve(): Any = PositiveInfinity
override val isInfinite: Boolean override val isInfinite: Boolean
@ -952,6 +957,9 @@ sealed class Decimal : Number(), Comparable<Decimal> {
} }
private object NegativeInfinity : Decimal() { private object NegativeInfinity : Decimal() {
override val mag: BigInteger
get() = throw UnsupportedOperationException()
private fun readResolve(): Any = NegativeInfinity private fun readResolve(): Any = NegativeInfinity
override val isInfinite: Boolean override val isInfinite: Boolean
@ -1195,6 +1203,9 @@ sealed class Decimal : Number(), Comparable<Decimal> {
} }
private object Zero : Decimal() { private object Zero : Decimal() {
override val mag: BigInteger
get() = BigInteger.ZERO
private fun readResolve(): Any = Zero private fun readResolve(): Any = Zero
override val isInfinite: Boolean override val isInfinite: Boolean