Much more lenient Decimal, solving much more problems than it can potentially create

This commit is contained in:
DBotThePony 2023-03-13 09:43:18 +07:00
parent 419097ce88
commit 1ed2eb2134
Signed by: DBot
GPG Key ID: DCC23B5715498507

View File

@ -149,8 +149,12 @@ fun Decimal(value: Long) = Decimal.valueOf(value)
* not guaranteed to be precise (stored as [Double]). The reason behind creation of this class, however, * not guaranteed to be precise (stored as [Double]). The reason behind creation of this class, however,
* is to allow infinite precision of [whole] part, while leaving [decimal] part to be rounded in inexact operations. * is to allow infinite precision of [whole] part, while leaving [decimal] part to be rounded in inexact operations.
* *
* This class is value based, however, [equals] and [compareTo] are not doing *exact* comparison. Whole part is always compared * This class is value based, hence it is immutable, and can not be utilized as synchronization lock, and should not be compared
* exactly, but decimal part is considered equal if their difference is less than [EPSILON]. * using identity (`===`) comparison.
*
* **In general, this class is lenient about precision**, to avoid gameplay-wise player frustration, rounding is placed in multiple parts of this class,
* including its constructor (e.g. it is possible to get `Decimal.ZERO == Decimal("0.00000001")`). [EPSILON] is the reference point of precision,
* any value lesser than [EPSILON] is considered zero (including difference between *the value* and (-)1.0).
*/ */
@Suppress("unused") @Suppress("unused")
class Decimal @JvmOverloads constructor(whole: BigInteger, decimal: Double = 0.0) : Number(), Comparable<Decimal>, java.io.Serializable { class Decimal @JvmOverloads constructor(whole: BigInteger, decimal: Double = 0.0) : Number(), Comparable<Decimal>, java.io.Serializable {
@ -176,6 +180,13 @@ class Decimal @JvmOverloads constructor(whole: BigInteger, decimal: Double = 0.0
init { init {
@Suppress("name_shadowing") @Suppress("name_shadowing")
var decimal = decimal var decimal = decimal
if (decimal < 1.0 && decimal + EPSILON >= 1.0) {
decimal = 1.0
} else if (decimal > -1.0 && decimal - EPSILON <= -1.0) {
decimal = -1.0
}
@Suppress("name_shadowing") @Suppress("name_shadowing")
var whole = whole var whole = whole
@ -553,18 +564,7 @@ class Decimal @JvmOverloads constructor(whole: BigInteger, decimal: Double = 0.0
return false return false
if (other is Decimal) { if (other is Decimal) {
return other.whole == whole && weakEqualDoubles(decimal, other.decimal) return other.whole == whole && decimal == other.decimal
}
return super.equals(other)
}
fun equalsStrict(other: Any?): Boolean {
if (isNaN)
return other is Decimal && other.isNaN // ибо hashCode() так требует
if (other is Decimal) {
return other.whole == whole && other.decimal == decimal
} }
return super.equals(other) return super.equals(other)
@ -574,7 +574,7 @@ class Decimal @JvmOverloads constructor(whole: BigInteger, decimal: Double = 0.0
if (isNaN) if (isNaN)
return Double.NaN.hashCode() return Double.NaN.hashCode()
return 31 * (decimal - decimal % EPSILON).hashCode() + whole.hashCode() return 31 * decimal.hashCode() + whole.hashCode()
} }
fun floor(): Decimal { fun floor(): Decimal {
@ -639,36 +639,10 @@ class Decimal @JvmOverloads constructor(whole: BigInteger, decimal: Double = 0.0
else if (a > b) else if (a > b)
return 1 return 1
if (other.whole == whole) { var cmp = whole.compareTo(other.whole)
return weakCompareDoubles(decimal, other.decimal) if (cmp == 0) cmp = decimal.compareTo(other.decimal)
}
return whole.compareTo(other.whole) return cmp
}
fun compareToStrict(other: Decimal): 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 toByteArray(): ByteArray { fun toByteArray(): ByteArray {