Fraction is now NaN capable
This commit is contained in:
parent
65aa3392ba
commit
394f387993
@ -43,7 +43,10 @@ inline fun invertCompare(int: Int): Int {
|
||||
return if (int < 0) 1 else -1
|
||||
}
|
||||
|
||||
private val BI_MINUS_ZERO = BigInteger("-0")
|
||||
|
||||
@JvmRecord
|
||||
@Suppress("unused")
|
||||
data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @JvmField val divisor: BigInteger = BigInteger.ONE, @JvmField val compact: Boolean = true) {
|
||||
@JvmOverloads constructor(value: Long, compact: Boolean = true) : this(BigInteger.valueOf(value), compact = compact)
|
||||
@JvmOverloads constructor(value: Int, compact: Boolean = true) : this(BigInteger.valueOf(value.toLong()), compact = compact)
|
||||
@ -69,6 +72,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
val c = divisor.signum()
|
||||
|
||||
if (a != b && a != c) {
|
||||
if (divisor == BigInteger.ZERO || divisor == BI_MINUS_ZERO)
|
||||
return Fraction(-value, -divisor)
|
||||
|
||||
val mod = value % divisor
|
||||
|
||||
if (mod == BigInteger.ZERO)
|
||||
@ -77,6 +83,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
return Fraction(-value, -divisor)
|
||||
}
|
||||
|
||||
if (divisor == BigInteger.ZERO || divisor == BI_MINUS_ZERO)
|
||||
return this
|
||||
|
||||
val mod = value % divisor
|
||||
|
||||
if (mod == BigInteger.ZERO)
|
||||
@ -86,13 +95,19 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
}
|
||||
|
||||
fun isZero(): Boolean {
|
||||
if (isNaN()) return false
|
||||
return value == BigInteger.ZERO
|
||||
}
|
||||
|
||||
fun compact(): Fraction {
|
||||
if (isNaN()) return this
|
||||
|
||||
if (value == BigInteger.ZERO || value == BigInteger.ONE || divisor == BigInteger.ONE)
|
||||
return this
|
||||
|
||||
if (divisor == BigInteger.ZERO || divisor == BI_MINUS_ZERO)
|
||||
return this
|
||||
|
||||
val mod = value % divisor
|
||||
|
||||
if (mod == BigInteger.ZERO)
|
||||
@ -102,6 +117,8 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
}
|
||||
|
||||
fun canonize(): Fraction {
|
||||
if (isNaN()) return this
|
||||
|
||||
if (value == BigInteger.ZERO || value == BigInteger.ONE || divisor == BigInteger.ONE || divisor == BigInteger.ZERO)
|
||||
return this
|
||||
|
||||
@ -120,6 +137,8 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
if (other == null)
|
||||
return false
|
||||
|
||||
if (isNaN() || other.isNaN()) return false
|
||||
|
||||
val a = compact()
|
||||
val b = other.compact()
|
||||
|
||||
@ -127,6 +146,8 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
}
|
||||
|
||||
operator fun compareTo(other: Fraction): Int {
|
||||
if (isNaN() || other.isNaN()) return 0
|
||||
|
||||
if (divisor == other.divisor)
|
||||
return value.compareTo(other.value)
|
||||
|
||||
@ -156,7 +177,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
return cmp
|
||||
}
|
||||
|
||||
fun plusCompact(other: Fraction): Fraction {
|
||||
private fun plusCompact(other: Fraction): Fraction {
|
||||
if (isNaN()) return this
|
||||
|
||||
if (divisor == other.divisor) {
|
||||
if (divisor == BigInteger.ONE)
|
||||
return Fraction(value + other.value)
|
||||
@ -166,6 +189,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
if (new.compareTo(BigInteger.ZERO) == 0)
|
||||
return ZERO
|
||||
|
||||
if (divisor == BigInteger.ZERO || divisor == BI_MINUS_ZERO)
|
||||
return Fraction(new, divisor)
|
||||
|
||||
val mod = new % divisor
|
||||
|
||||
if (mod == BigInteger.ZERO)
|
||||
@ -176,6 +202,10 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
|
||||
val new = value * other.divisor + other.value * divisor
|
||||
val div = divisor * other.divisor
|
||||
|
||||
if (div == BigInteger.ZERO || div == BI_MINUS_ZERO)
|
||||
return Fraction(new, div)
|
||||
|
||||
val mod = new % div
|
||||
|
||||
if (mod == BigInteger.ZERO)
|
||||
@ -185,6 +215,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
}
|
||||
|
||||
operator fun plus(other: Fraction): Fraction {
|
||||
if (isNaN()) return this
|
||||
if (other.isNaN()) return other
|
||||
|
||||
if (compact)
|
||||
return plusCompact(other)
|
||||
|
||||
@ -195,7 +228,7 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
return Fraction(value * other.divisor + other.value * divisor, divisor * other.divisor, compact = false)
|
||||
}
|
||||
|
||||
fun minusCompact(other: Fraction): Fraction {
|
||||
private fun minusCompact(other: Fraction): Fraction {
|
||||
if (divisor == other.divisor) {
|
||||
if (divisor == BigInteger.ONE)
|
||||
return Fraction(value - other.value)
|
||||
@ -205,6 +238,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
if (new.compareTo(BigInteger.ZERO) == 0)
|
||||
return ZERO
|
||||
|
||||
if (divisor == BigInteger.ZERO || divisor == BI_MINUS_ZERO)
|
||||
return Fraction(new, divisor)
|
||||
|
||||
val mod = new % divisor
|
||||
|
||||
if (mod == BigInteger.ZERO)
|
||||
@ -215,6 +251,10 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
|
||||
val new = value * other.divisor - other.value * divisor
|
||||
val div = divisor * other.divisor
|
||||
|
||||
if (div == BigInteger.ZERO || div == BI_MINUS_ZERO)
|
||||
return Fraction(new, div)
|
||||
|
||||
val mod = new % div
|
||||
|
||||
if (mod == BigInteger.ZERO)
|
||||
@ -224,6 +264,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
}
|
||||
|
||||
operator fun minus(other: Fraction): Fraction {
|
||||
if (isNaN()) return this
|
||||
if (other.isNaN()) return other
|
||||
|
||||
if (compact)
|
||||
return minusCompact(other)
|
||||
|
||||
@ -234,13 +277,17 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
return Fraction(value * other.divisor - other.value * divisor, divisor * other.divisor, compact = false)
|
||||
}
|
||||
|
||||
fun timesCompact(other: Fraction): Fraction {
|
||||
private fun timesCompact(other: Fraction): Fraction {
|
||||
val new = value * other.value
|
||||
|
||||
if (new.compareTo(BigInteger.ZERO) == 0)
|
||||
return ZERO
|
||||
|
||||
val div = divisor * other.divisor
|
||||
|
||||
if (div == BigInteger.ZERO || div == BI_MINUS_ZERO)
|
||||
return Fraction(new, div)
|
||||
|
||||
val mod = new % div
|
||||
|
||||
if (mod == BigInteger.ZERO)
|
||||
@ -250,19 +297,26 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
}
|
||||
|
||||
operator fun times(other: Fraction): Fraction {
|
||||
if (isNaN()) return this
|
||||
if (other.isNaN()) return other
|
||||
|
||||
if (compact)
|
||||
return timesCompact(other)
|
||||
|
||||
return Fraction(value * other.value, divisor * other.divisor, compact = false)
|
||||
}
|
||||
|
||||
fun divCompact(other: Fraction): Fraction {
|
||||
private fun divCompact(other: Fraction): Fraction {
|
||||
val new = value * other.divisor
|
||||
|
||||
if (new.compareTo(BigInteger.ZERO) == 0)
|
||||
return ZERO
|
||||
|
||||
val div = divisor * other.value
|
||||
|
||||
if (div == BigInteger.ZERO || div == BI_MINUS_ZERO)
|
||||
return Fraction(new, div)
|
||||
|
||||
val mod = new % div
|
||||
|
||||
if (mod == BigInteger.ZERO)
|
||||
@ -272,6 +326,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
}
|
||||
|
||||
operator fun div(other: Fraction): Fraction {
|
||||
if (isNaN()) return this
|
||||
if (other.isNaN()) return other
|
||||
|
||||
if (compact)
|
||||
return divCompact(other)
|
||||
|
||||
@ -279,6 +336,7 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
}
|
||||
|
||||
operator fun unaryMinus(): Fraction {
|
||||
if (isNaN()) return this
|
||||
return Fraction(-value, divisor)
|
||||
}
|
||||
|
||||
@ -347,19 +405,27 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
*/
|
||||
|
||||
operator fun rem(other: Fraction): Fraction {
|
||||
if (isNaN()) return this
|
||||
if (other.isNaN()) return other
|
||||
return Fraction((this / other).wholePart())
|
||||
}
|
||||
|
||||
// Преобразования
|
||||
fun toFloat(): Float {
|
||||
if (isNaN()) return Float.NaN
|
||||
return (value / divisor).toFloat() + ((value % divisor).toFloat() / divisor.toFloat())
|
||||
}
|
||||
|
||||
fun toDouble(): Double {
|
||||
if (isNaN()) return Double.NaN
|
||||
return (value / divisor).toDouble() + ((value % divisor).toDouble() / divisor.toDouble())
|
||||
}
|
||||
|
||||
fun toByteArray(): ByteArray {
|
||||
if (isNaN()) {
|
||||
return byteArrayOf(1, 0, 1, 0)
|
||||
}
|
||||
|
||||
val bytesA = value.toByteArray()
|
||||
val bytesB = divisor.toByteArray()
|
||||
|
||||
@ -367,41 +433,52 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
}
|
||||
|
||||
fun toInt(): Int {
|
||||
if (isNaN()) throw ArithmeticException("Fraction is not a number")
|
||||
return (value / divisor).toInt()
|
||||
}
|
||||
|
||||
fun toLong(): Long {
|
||||
if (isNaN()) throw ArithmeticException("Fraction is not a number")
|
||||
return (value / divisor).toLong()
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun toBigDecimal(context: MathContext = DEFAULT_MATH_CONTEXT): BigDecimal {
|
||||
if (isNaN()) throw ArithmeticException("Fraction is not a number")
|
||||
return BigDecimal(value).divide(BigDecimal(divisor), context)
|
||||
}
|
||||
|
||||
// Утилиты
|
||||
fun wholePart(): BigInteger {
|
||||
if (isNaN()) throw ArithmeticException("Fraction is not a number")
|
||||
return value / divisor
|
||||
}
|
||||
|
||||
fun fractionPart(): Fraction {
|
||||
if (isNaN()) return this
|
||||
return Fraction(value % divisor, divisor)
|
||||
}
|
||||
|
||||
fun modPart(): BigInteger {
|
||||
if (isNaN()) throw ArithmeticException("Fraction is not a number")
|
||||
return value % divisor
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
if (isNaN()) return "NaN"
|
||||
|
||||
return "$value/$divisor"
|
||||
}
|
||||
|
||||
fun formattedString(): String {
|
||||
if (isNaN()) return "NaN"
|
||||
return "${wholePart()} ${modPart()}/$divisor"
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun decimalString(nums: Int = 2, strict: Boolean = false): String {
|
||||
if (isNaN()) return "NaN"
|
||||
|
||||
val whole = wholePart()
|
||||
val fraction = modPart()
|
||||
|
||||
@ -438,6 +515,8 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
}
|
||||
|
||||
fun signnum(): Int {
|
||||
if (isNaN()) return 0
|
||||
|
||||
val a = value.signum()
|
||||
val b = divisor.signum()
|
||||
|
||||
@ -503,7 +582,10 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
|
||||
// Позволяет получить процент от деления данного на divisor с точностью до 5 цифр
|
||||
fun percentage(divisor: Fraction): Float {
|
||||
if (isNaN() || divisor.isNaN()) return Float.NaN
|
||||
|
||||
val mul = (this * TEN_THOUSAND) / divisor
|
||||
if (mul.isNaN()) return Float.NaN
|
||||
|
||||
return mul.wholePart().toFloat() / 10_000f
|
||||
}
|
||||
@ -512,6 +594,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
|
||||
@JvmField
|
||||
val ZERO = Fraction(BigInteger.ZERO)
|
||||
|
||||
@JvmField
|
||||
val NaN = Fraction(BigInteger.ZERO, BigInteger.ZERO)
|
||||
|
||||
@JvmField
|
||||
val ONE_HUNDRED = Fraction(BigInteger.valueOf(100))
|
||||
|
||||
|
@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.tests
|
||||
|
||||
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.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
@ -36,6 +37,34 @@ object FractionTests {
|
||||
println("canonical -1/-2 == ${Fraction(-1, -2).canonize()}")
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Fraction NaN behavior")
|
||||
fun nan() {
|
||||
assert((Fraction.NaN + Fraction.ONE).isNaN())
|
||||
assert((Fraction.NaN - Fraction.ONE).isNaN())
|
||||
assert((Fraction.NaN / Fraction.ONE).isNaN())
|
||||
assert((Fraction.NaN * Fraction.ONE).isNaN())
|
||||
|
||||
assert((Fraction.ONE + Fraction.NaN).isNaN())
|
||||
assert((Fraction.ONE - Fraction.NaN).isNaN())
|
||||
assert((Fraction.ONE / Fraction.NaN).isNaN())
|
||||
assert((Fraction.ONE * Fraction.NaN).isNaN())
|
||||
|
||||
assert((Fraction.ONE / Fraction.ZERO).isNaN())
|
||||
assert((Fraction.ONE * Fraction(0, 0)).isNaN())
|
||||
assert((Fraction.ONE * Fraction(1, 0)).isNaN())
|
||||
|
||||
assert(Fraction.NaN.toFloat().isNaN())
|
||||
assert(Fraction.NaN.toDouble().isNaN())
|
||||
|
||||
assertThrows<ArithmeticException> {Fraction.NaN.toInt()}
|
||||
assertThrows<ArithmeticException> {Fraction.NaN.toLong()}
|
||||
assertThrows<ArithmeticException> {Fraction.NaN.wholePart()}
|
||||
|
||||
assert(Fraction.fromByteArray(Fraction.NaN.toByteArray()).isNaN())
|
||||
assert(Fraction.NaN.percentage(Fraction.ONE).isNaN())
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Fraction comparison")
|
||||
fun equality() {
|
||||
|
Loading…
Reference in New Issue
Block a user