Skip to content

Commit

Permalink
EF helper functions (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
lbressler13 authored Jan 31, 2024
1 parent 3cc4f44 commit 0be734c
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 208 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ package xyz.lbres.exactnumbers.exactfraction

import xyz.lbres.common.divideByZero
import xyz.lbres.exactnumbers.ext.eq
import xyz.lbres.exactnumbers.ext.toExactFraction
import xyz.lbres.kotlinutils.biginteger.ext.ifZero
import xyz.lbres.kotlinutils.biginteger.ext.isNegative
import xyz.lbres.kotlinutils.biginteger.ext.isZero
import xyz.lbres.kotlinutils.biginteger.getGCD
import java.math.BigDecimal
import java.math.BigInteger
import java.math.MathContext
Expand Down Expand Up @@ -43,9 +40,13 @@ class ExactFraction private constructor() : Comparable<ExactFraction>, Number()
* @throws ArithmeticException if denominator is 0
*/
constructor (numerator: BigInteger, denominator: BigInteger) : this() {
this.numerator = numerator
this.denominator = denominator.ifZero { throw divideByZero }
simplify()
if (denominator.isZero()) {
throw divideByZero
}

val simplified = simplifyFraction(numerator, denominator)
this.numerator = simplified.first
this.denominator = simplified.second
}

/**
Expand Down Expand Up @@ -91,44 +92,25 @@ class ExactFraction private constructor() : Comparable<ExactFraction>, Number()

// BINARY OPERATORS

operator fun plus(other: ExactFraction): ExactFraction {
if (denominator == other.denominator) {
val newNumerator = numerator + other.numerator
return ExactFraction(newNumerator, denominator)
}

val scaled1 = numerator * other.denominator
val scaled2 = other.numerator * denominator

val newNumerator = scaled1 + scaled2
val newDenominator = denominator * other.denominator
return ExactFraction(newNumerator, newDenominator)
}

operator fun plus(other: BigInteger): ExactFraction = plus(other.toExactFraction())
operator fun plus(other: Long): ExactFraction = plus(other.toExactFraction())
operator fun plus(other: Int): ExactFraction = plus(other.toExactFraction())
operator fun plus(other: ExactFraction): ExactFraction = efAdd(this, other)
operator fun plus(other: BigInteger): ExactFraction = plus(ExactFraction(other))
operator fun plus(other: Long): ExactFraction = plus(ExactFraction(other))
operator fun plus(other: Int): ExactFraction = plus(ExactFraction(other))

operator fun minus(other: ExactFraction): ExactFraction = plus(-other)
operator fun minus(other: BigInteger): ExactFraction = plus(-other)
operator fun minus(other: Long): ExactFraction = plus(-other)
operator fun minus(other: Int): ExactFraction = plus(-other)

operator fun times(other: ExactFraction): ExactFraction {
val newNumerator = numerator * other.numerator
val newDenominator = denominator * other.denominator
return ExactFraction(newNumerator, newDenominator)
}
operator fun minus(other: BigInteger): ExactFraction = minus(ExactFraction(other))
operator fun minus(other: Long): ExactFraction = minus(ExactFraction(other))
operator fun minus(other: Int): ExactFraction = minus(ExactFraction(other))

operator fun times(other: BigInteger): ExactFraction = times(other.toExactFraction())
operator fun times(other: Long): ExactFraction = times(other.toExactFraction())
operator fun times(other: Int): ExactFraction = times(other.toExactFraction())
operator fun times(other: ExactFraction): ExactFraction = efTimes(this, other)
operator fun times(other: BigInteger): ExactFraction = times(ExactFraction(other))
operator fun times(other: Long): ExactFraction = times(ExactFraction(other))
operator fun times(other: Int): ExactFraction = times(ExactFraction(other))

operator fun div(other: ExactFraction): ExactFraction = times(other.inverse())

operator fun div(other: BigInteger): ExactFraction = div(other.toExactFraction())
operator fun div(other: Long): ExactFraction = div(other.toExactFraction())
operator fun div(other: Int): ExactFraction = div(other.toExactFraction())
operator fun div(other: BigInteger): ExactFraction = div(ExactFraction(other))
operator fun div(other: Long): ExactFraction = div(ExactFraction(other))
operator fun div(other: Int): ExactFraction = div(ExactFraction(other))

override fun equals(other: Any?): Boolean {
if (other == null || other !is ExactFraction) {
Expand All @@ -140,53 +122,17 @@ class ExactFraction private constructor() : Comparable<ExactFraction>, Number()
return scaled1 == scaled2
}

fun eq(other: Int): Boolean = numerator.eq(other) && denominator.eq(1)
fun eq(other: Long): Boolean = numerator.eq(other) && denominator.eq(1)
fun eq(other: Int): Boolean = eq(other.toBigInteger())
fun eq(other: Long): Boolean = eq(other.toBigInteger())
fun eq(other: BigInteger): Boolean = numerator == other && denominator.eq(1)

override fun compareTo(other: ExactFraction): Int {
val difference = minus(other)
return when {
difference.isNegative() -> -1
difference.isZero() -> 0
else -> 1
}
}
override fun compareTo(other: ExactFraction): Int = efCompare(this, other)

operator fun compareTo(other: Int): Int = compareTo(other.toExactFraction())
operator fun compareTo(other: Long): Int = compareTo(other.toExactFraction())
operator fun compareTo(other: BigInteger): Int = compareTo(other.toExactFraction())
operator fun compareTo(other: Int): Int = compareTo(ExactFraction(other))
operator fun compareTo(other: Long): Int = compareTo(ExactFraction(other))
operator fun compareTo(other: BigInteger): Int = compareTo(ExactFraction(other))

fun pow(other: ExactFraction): ExactFraction {
if (other.isZero()) {
return ONE
}

if (other.denominator != BigInteger.ONE) {
throw ArithmeticException("Exponents must be whole numbers")
}

var numeratorMult = BigInteger.ONE
var denominatorMult = BigInteger.ONE
var remaining = other.absoluteValue().numerator.abs()
val intMax = Int.MAX_VALUE

while (remaining > BigInteger.ZERO) {
if (remaining > intMax.toBigInteger()) {
numeratorMult *= numerator.pow(intMax)
denominatorMult *= denominator.pow(intMax)
remaining -= intMax.toBigInteger()
} else {
val exp = remaining.toInt()
numeratorMult = numerator.pow(exp)
denominatorMult = denominator.pow(exp)
remaining = BigInteger.ZERO
}
}

val result = ExactFraction(numeratorMult, denominatorMult)
return if (other < 0) result.inverse() else result
}
fun pow(other: ExactFraction): ExactFraction = efPow(this, other)

// UNARY NON-OPERATORS

Expand All @@ -202,53 +148,6 @@ class ExactFraction private constructor() : Comparable<ExactFraction>, Number()
fun isNegative(): Boolean = numerator.isNegative()
fun isZero(): Boolean = numerator.isZero()

// SIMPLIFICATION

private fun simplify() {
simplifyZero()
simplifyGCD()
simplifySign()
}

/**
* Set denominator to 1 when numerator is 0
*/
private fun simplifyZero() {
if (numerator.eq(0)) {
denominator = BigInteger.ONE
}
}

/**
* Move negatives to numerator
*/
private fun simplifySign() {
val numNegative = numerator.isNegative()
val denomNegative = denominator.isNegative()

when {
numNegative && denomNegative -> {
numerator = numerator.abs()
denominator = denominator.abs()
}
!numNegative && denomNegative -> {
numerator = -numerator
denominator = denominator.abs()
}
}
}

/**
* Simplify using greatest common divisor
*/
private fun simplifyGCD() {
if (!numerator.isZero()) {
val gcd = getGCD(numerator, denominator)
numerator /= gcd
denominator /= gcd
}
}

// STRING METHODS

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package xyz.lbres.exactnumbers.exactfraction

import xyz.lbres.kotlinutils.biginteger.ext.isNegative
import xyz.lbres.kotlinutils.biginteger.ext.isZero
import xyz.lbres.kotlinutils.biginteger.getGCD
import java.math.BigInteger

/**
* Simplify numerator and denominator values to smallest values with same ratio, and move all negatives into numerator
*
* @param numerator [BigInteger]: numerator of fraction to simplify
* @param denominator [BigInteger]: denominator of fraction to simplify
* @return Pair<BigInteger, BigInteger>: pair where first value represents simplified numerator, and second value represents simplified denominator
*/
internal fun simplifyFraction(numerator: BigInteger, denominator: BigInteger): Pair<BigInteger, BigInteger> {
// set denominator to 1 when numerator is 0
if (numerator.isZero()) {
return Pair(BigInteger.ZERO, BigInteger.ONE)
}

// simplify using greatest common divisor
val gcd = getGCD(numerator, denominator)
val simplifiedNumerator = numerator / gcd
val simplifiedDenominator = denominator / gcd

// move negatives to numerator
return if (denominator.isNegative()) {
Pair(-simplifiedNumerator, -simplifiedDenominator)
} else {
Pair(simplifiedNumerator, simplifiedDenominator)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package xyz.lbres.exactnumbers.exactfraction

import java.math.BigInteger

/**
* Add two ExactFractions
*
* @param ef1 [ExactFraction]
* @param ef2 [ExactFraction]
* @return [ExactFraction]: sum of [ef1] and [ef2]
*/
internal fun efAdd(ef1: ExactFraction, ef2: ExactFraction): ExactFraction {
if (ef1.denominator == ef2.denominator) {
val newNumerator = ef1.numerator + ef2.numerator
return ExactFraction(newNumerator, ef1.denominator)
}

val newNumerator = ef1.numerator * ef2.denominator + ef2.numerator * ef1.denominator
val newDenominator = ef1.denominator * ef2.denominator
return ExactFraction(newNumerator, newDenominator)
}

/**
* Multiply two ExactFractions
*
* @param ef1 [ExactFraction]
* @param ef2 [ExactFraction]
* @return [ExactFraction]: product of [ef1] and [ef2]
*/
internal fun efTimes(ef1: ExactFraction, ef2: ExactFraction): ExactFraction {
val newNumerator = ef1.numerator * ef2.numerator
val newDenominator = ef1.denominator * ef2.denominator
return ExactFraction(newNumerator, newDenominator)
}

/**
* Apply exponent to an ExactFraction
*
* @param base [ExactFraction]: base of exponentiation
* @param exponent [ExactFraction]: exponent, must be a whole number
* @return [ExactFraction]
*/
internal fun efPow(base: ExactFraction, exponent: ExactFraction): ExactFraction {
// TODO
// if (!exponent.isWholeNumber()) {
if (exponent.denominator != BigInteger.ONE) {
throw ArithmeticException("Exponents must be whole numbers")
}

when {
base == ExactFraction.ONE || exponent.isZero() -> return ExactFraction.ONE
base.isZero() -> return ExactFraction.ZERO
exponent == ExactFraction.ONE -> return base
}

var powNumerator = BigInteger.ONE
var powDenominator = BigInteger.ONE
var remaining = exponent.numerator.abs()
val intMax = Int.MAX_VALUE

try {
while (remaining > BigInteger.ZERO) {
if (remaining > intMax.toBigInteger()) {
powNumerator *= base.numerator.pow(intMax)
powDenominator *= base.denominator.pow(intMax)
remaining -= intMax.toBigInteger()
} else {
val exp = remaining.toInt()
powNumerator = base.numerator.pow(exp)
powDenominator = base.denominator.pow(exp)
remaining = BigInteger.ZERO
}
}
} catch (e: ArithmeticException) {
if (e.message == "BigInteger would overflow supported range") {
throw ExactFractionOverflowException()
}

throw e
}

return if (exponent.isNegative()) {
ExactFraction(powDenominator, powNumerator)
} else {
ExactFraction(powNumerator, powDenominator)
}
}

/**
* Compare two ExactFractions
*
* @param ef1 [ExactFraction]
* @param ef2 [ExactFraction]
* @return [Int]: comparison of [ef1] to [ef2]
*/
internal fun efCompare(ef1: ExactFraction, ef2: ExactFraction): Int {
val difference = ef1 - ef2
return when {
difference.isNegative() -> -1
difference.isZero() -> 0
else -> 1
}
}
Loading

0 comments on commit 0be734c

Please sign in to comment.