Fraction is now NaN capable

This commit is contained in:
DBotThePony 2021-12-28 23:45:03 +07:00
parent 65aa3392ba
commit 394f387993
Signed by: DBot
GPG Key ID: DCC23B5715498507
2 changed files with 118 additions and 4 deletions

View File

@ -43,7 +43,10 @@ inline fun invertCompare(int: Int): Int {
return if (int < 0) 1 else -1 return if (int < 0) 1 else -1
} }
private val BI_MINUS_ZERO = BigInteger("-0")
@JvmRecord @JvmRecord
@Suppress("unused")
data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @JvmField val divisor: BigInteger = BigInteger.ONE, @JvmField val compact: Boolean = true) { 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: Long, compact: Boolean = true) : this(BigInteger.valueOf(value), compact = compact)
@JvmOverloads constructor(value: Int, compact: Boolean = true) : this(BigInteger.valueOf(value.toLong()), 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() val c = divisor.signum()
if (a != b && a != c) { if (a != b && a != c) {
if (divisor == BigInteger.ZERO || divisor == BI_MINUS_ZERO)
return Fraction(-value, -divisor)
val mod = value % divisor val mod = value % divisor
if (mod == BigInteger.ZERO) if (mod == BigInteger.ZERO)
@ -77,6 +83,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
return Fraction(-value, -divisor) return Fraction(-value, -divisor)
} }
if (divisor == BigInteger.ZERO || divisor == BI_MINUS_ZERO)
return this
val mod = value % divisor val mod = value % divisor
if (mod == BigInteger.ZERO) if (mod == BigInteger.ZERO)
@ -86,13 +95,19 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
} }
fun isZero(): Boolean { fun isZero(): Boolean {
if (isNaN()) return false
return value == BigInteger.ZERO return value == BigInteger.ZERO
} }
fun compact(): Fraction { fun compact(): Fraction {
if (isNaN()) return this
if (value == BigInteger.ZERO || value == BigInteger.ONE || divisor == BigInteger.ONE) if (value == BigInteger.ZERO || value == BigInteger.ONE || divisor == BigInteger.ONE)
return this return this
if (divisor == BigInteger.ZERO || divisor == BI_MINUS_ZERO)
return this
val mod = value % divisor val mod = value % divisor
if (mod == BigInteger.ZERO) if (mod == BigInteger.ZERO)
@ -102,6 +117,8 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
} }
fun canonize(): Fraction { fun canonize(): Fraction {
if (isNaN()) return this
if (value == BigInteger.ZERO || value == BigInteger.ONE || divisor == BigInteger.ONE || divisor == BigInteger.ZERO) if (value == BigInteger.ZERO || value == BigInteger.ONE || divisor == BigInteger.ONE || divisor == BigInteger.ZERO)
return this return this
@ -120,6 +137,8 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
if (other == null) if (other == null)
return false return false
if (isNaN() || other.isNaN()) return false
val a = compact() val a = compact()
val b = other.compact() val b = other.compact()
@ -127,6 +146,8 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
} }
operator fun compareTo(other: Fraction): Int { operator fun compareTo(other: Fraction): Int {
if (isNaN() || other.isNaN()) return 0
if (divisor == other.divisor) if (divisor == other.divisor)
return value.compareTo(other.value) return value.compareTo(other.value)
@ -156,7 +177,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
return cmp return cmp
} }
fun plusCompact(other: Fraction): Fraction { private fun plusCompact(other: Fraction): Fraction {
if (isNaN()) return this
if (divisor == other.divisor) { if (divisor == other.divisor) {
if (divisor == BigInteger.ONE) if (divisor == BigInteger.ONE)
return Fraction(value + other.value) return Fraction(value + other.value)
@ -166,6 +189,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
if (new.compareTo(BigInteger.ZERO) == 0) if (new.compareTo(BigInteger.ZERO) == 0)
return ZERO return ZERO
if (divisor == BigInteger.ZERO || divisor == BI_MINUS_ZERO)
return Fraction(new, divisor)
val mod = new % divisor val mod = new % divisor
if (mod == BigInteger.ZERO) 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 new = value * other.divisor + other.value * divisor
val div = divisor * other.divisor val div = divisor * other.divisor
if (div == BigInteger.ZERO || div == BI_MINUS_ZERO)
return Fraction(new, div)
val mod = new % div val mod = new % div
if (mod == BigInteger.ZERO) if (mod == BigInteger.ZERO)
@ -185,6 +215,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
} }
operator fun plus(other: Fraction): Fraction { operator fun plus(other: Fraction): Fraction {
if (isNaN()) return this
if (other.isNaN()) return other
if (compact) if (compact)
return plusCompact(other) 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) 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 == other.divisor) {
if (divisor == BigInteger.ONE) if (divisor == BigInteger.ONE)
return Fraction(value - other.value) return Fraction(value - other.value)
@ -205,6 +238,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
if (new.compareTo(BigInteger.ZERO) == 0) if (new.compareTo(BigInteger.ZERO) == 0)
return ZERO return ZERO
if (divisor == BigInteger.ZERO || divisor == BI_MINUS_ZERO)
return Fraction(new, divisor)
val mod = new % divisor val mod = new % divisor
if (mod == BigInteger.ZERO) 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 new = value * other.divisor - other.value * divisor
val div = divisor * other.divisor val div = divisor * other.divisor
if (div == BigInteger.ZERO || div == BI_MINUS_ZERO)
return Fraction(new, div)
val mod = new % div val mod = new % div
if (mod == BigInteger.ZERO) if (mod == BigInteger.ZERO)
@ -224,6 +264,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
} }
operator fun minus(other: Fraction): Fraction { operator fun minus(other: Fraction): Fraction {
if (isNaN()) return this
if (other.isNaN()) return other
if (compact) if (compact)
return minusCompact(other) 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) 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 val new = value * other.value
if (new.compareTo(BigInteger.ZERO) == 0) if (new.compareTo(BigInteger.ZERO) == 0)
return ZERO return ZERO
val div = divisor * other.divisor val div = divisor * other.divisor
if (div == BigInteger.ZERO || div == BI_MINUS_ZERO)
return Fraction(new, div)
val mod = new % div val mod = new % div
if (mod == BigInteger.ZERO) if (mod == BigInteger.ZERO)
@ -250,19 +297,26 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
} }
operator fun times(other: Fraction): Fraction { operator fun times(other: Fraction): Fraction {
if (isNaN()) return this
if (other.isNaN()) return other
if (compact) if (compact)
return timesCompact(other) return timesCompact(other)
return Fraction(value * other.value, divisor * other.divisor, compact = false) 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 val new = value * other.divisor
if (new.compareTo(BigInteger.ZERO) == 0) if (new.compareTo(BigInteger.ZERO) == 0)
return ZERO return ZERO
val div = divisor * other.value val div = divisor * other.value
if (div == BigInteger.ZERO || div == BI_MINUS_ZERO)
return Fraction(new, div)
val mod = new % div val mod = new % div
if (mod == BigInteger.ZERO) if (mod == BigInteger.ZERO)
@ -272,6 +326,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
} }
operator fun div(other: Fraction): Fraction { operator fun div(other: Fraction): Fraction {
if (isNaN()) return this
if (other.isNaN()) return other
if (compact) if (compact)
return divCompact(other) return divCompact(other)
@ -279,6 +336,7 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
} }
operator fun unaryMinus(): Fraction { operator fun unaryMinus(): Fraction {
if (isNaN()) return this
return Fraction(-value, divisor) return Fraction(-value, divisor)
} }
@ -347,19 +405,27 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
*/ */
operator fun rem(other: Fraction): Fraction { operator fun rem(other: Fraction): Fraction {
if (isNaN()) return this
if (other.isNaN()) return other
return Fraction((this / other).wholePart()) return Fraction((this / other).wholePart())
} }
// Преобразования // Преобразования
fun toFloat(): Float { fun toFloat(): Float {
if (isNaN()) return Float.NaN
return (value / divisor).toFloat() + ((value % divisor).toFloat() / divisor.toFloat()) return (value / divisor).toFloat() + ((value % divisor).toFloat() / divisor.toFloat())
} }
fun toDouble(): Double { fun toDouble(): Double {
if (isNaN()) return Double.NaN
return (value / divisor).toDouble() + ((value % divisor).toDouble() / divisor.toDouble()) return (value / divisor).toDouble() + ((value % divisor).toDouble() / divisor.toDouble())
} }
fun toByteArray(): ByteArray { fun toByteArray(): ByteArray {
if (isNaN()) {
return byteArrayOf(1, 0, 1, 0)
}
val bytesA = value.toByteArray() val bytesA = value.toByteArray()
val bytesB = divisor.toByteArray() val bytesB = divisor.toByteArray()
@ -367,41 +433,52 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
} }
fun toInt(): Int { fun toInt(): Int {
if (isNaN()) throw ArithmeticException("Fraction is not a number")
return (value / divisor).toInt() return (value / divisor).toInt()
} }
fun toLong(): Long { fun toLong(): Long {
if (isNaN()) throw ArithmeticException("Fraction is not a number")
return (value / divisor).toLong() return (value / divisor).toLong()
} }
@JvmOverloads @JvmOverloads
fun toBigDecimal(context: MathContext = DEFAULT_MATH_CONTEXT): BigDecimal { fun toBigDecimal(context: MathContext = DEFAULT_MATH_CONTEXT): BigDecimal {
if (isNaN()) throw ArithmeticException("Fraction is not a number")
return BigDecimal(value).divide(BigDecimal(divisor), context) return BigDecimal(value).divide(BigDecimal(divisor), context)
} }
// Утилиты // Утилиты
fun wholePart(): BigInteger { fun wholePart(): BigInteger {
if (isNaN()) throw ArithmeticException("Fraction is not a number")
return value / divisor return value / divisor
} }
fun fractionPart(): Fraction { fun fractionPart(): Fraction {
if (isNaN()) return this
return Fraction(value % divisor, divisor) return Fraction(value % divisor, divisor)
} }
fun modPart(): BigInteger { fun modPart(): BigInteger {
if (isNaN()) throw ArithmeticException("Fraction is not a number")
return value % divisor return value % divisor
} }
override fun toString(): String { override fun toString(): String {
if (isNaN()) return "NaN"
return "$value/$divisor" return "$value/$divisor"
} }
fun formattedString(): String { fun formattedString(): String {
if (isNaN()) return "NaN"
return "${wholePart()} ${modPart()}/$divisor" return "${wholePart()} ${modPart()}/$divisor"
} }
@JvmOverloads @JvmOverloads
fun decimalString(nums: Int = 2, strict: Boolean = false): String { fun decimalString(nums: Int = 2, strict: Boolean = false): String {
if (isNaN()) return "NaN"
val whole = wholePart() val whole = wholePart()
val fraction = modPart() val fraction = modPart()
@ -438,6 +515,8 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
} }
fun signnum(): Int { fun signnum(): Int {
if (isNaN()) return 0
val a = value.signum() val a = value.signum()
val b = divisor.signum() val b = divisor.signum()
@ -503,7 +582,10 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
// Позволяет получить процент от деления данного на divisor с точностью до 5 цифр // Позволяет получить процент от деления данного на divisor с точностью до 5 цифр
fun percentage(divisor: Fraction): Float { fun percentage(divisor: Fraction): Float {
if (isNaN() || divisor.isNaN()) return Float.NaN
val mul = (this * TEN_THOUSAND) / divisor val mul = (this * TEN_THOUSAND) / divisor
if (mul.isNaN()) return Float.NaN
return mul.wholePart().toFloat() / 10_000f return mul.wholePart().toFloat() / 10_000f
} }
@ -512,6 +594,9 @@ data class Fraction @JvmOverloads constructor(@JvmField val value: BigInteger, @
@JvmField @JvmField
val ZERO = Fraction(BigInteger.ZERO) val ZERO = Fraction(BigInteger.ZERO)
@JvmField
val NaN = Fraction(BigInteger.ZERO, BigInteger.ZERO)
@JvmField @JvmField
val ONE_HUNDRED = Fraction(BigInteger.valueOf(100)) val ONE_HUNDRED = Fraction(BigInteger.valueOf(100))

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.tests
import org.junit.jupiter.api.DisplayName 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 ru.dbotthepony.mc.otm.core.Fraction import ru.dbotthepony.mc.otm.core.Fraction
import java.math.BigDecimal import java.math.BigDecimal
import java.math.BigInteger import java.math.BigInteger
@ -36,6 +37,34 @@ object FractionTests {
println("canonical -1/-2 == ${Fraction(-1, -2).canonize()}") 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 @Test
@DisplayName("Fraction comparison") @DisplayName("Fraction comparison")
fun equality() { fun equality() {