Last active
December 18, 2015 21:09
-
-
Save martintrojer/5845678 to your computer and use it in GitHub Desktop.
Ratio
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package frins | |
class Ratio(n: BigInt, d: BigInt) { | |
require(denom != 0) | |
private val g = gcd(n.abs, d.abs) | |
val numer: BigInt = n / g | |
val denom: BigInt = d / g | |
private def gcd(a: BigInt, b: BigInt): BigInt = | |
if (b == 0) a else gcd(b, a % b) | |
val bigdecValue: BigDecimal = BigDecimal(numer) / BigDecimal(denom) | |
// --- | |
def +(that: Ratio): Ratio = | |
new Ratio( | |
numer * that.denom + that.numer * denom, | |
denom * that.denom | |
) | |
def -(that: Ratio): Ratio = | |
new Ratio( | |
numer * that.denom - that.numer * denom, | |
denom * that.denom | |
) | |
def *(that: Ratio): Ratio = | |
new Ratio(numer * that.numer, denom * that.denom) | |
def /(that: Ratio): Ratio = | |
new Ratio(numer * that.denom, denom * that.numer) | |
// --- | |
override def toString = numer + (if (denom != 1) ("/"+ denom) else "") | |
override def equals(that: Any) = that match { | |
case that: Ratio => numer == that.numer && denom == that.denom | |
case _ => false | |
} | |
override val hashCode = 41 * numer.hashCode() + denom.hashCode() | |
} | |
object Ratio { | |
def apply(i: Int): Ratio = new Ratio(i,1) | |
def apply(n: Int, d: Int): Ratio = new Ratio(n ,d) | |
type BigDec = java.math.BigDecimal | |
def apply(b: BigDec): Ratio = { | |
val bv = b.unscaledValue() | |
val scale = b.scale() | |
if(scale < 0) | |
new Ratio(bv.multiply(BigInteger.TEN.pow(-scale)), BigInteger.ONE); | |
else | |
new Ratio(bv, BigInteger.TEN.pow(scale)); | |
} | |
def apply(d: Double): Ratio = { | |
// naive, slow and inaccurate | |
var up = 1 | |
var low = 1 | |
var df = 1.0 | |
val threshold = 0.0001 | |
while((d % threshold) != (df % threshold)) { | |
if (df < d) up += 1 | |
else { | |
low += 1 | |
up = d.toInt * low | |
} | |
df = up.toDouble / low | |
} | |
new Ratio(up, low) | |
} | |
implicit object RatioIsFractionalAndOrdered extends RatioIsFractional with RatioOrdering | |
implicit def intToRatio(i: Int) = apply(i) | |
implicit def doubleToRatio(d: Double) = apply(d) | |
} | |
trait RatioIsFractional extends Fractional[Ratio] { | |
def plus(x: Ratio, y: Ratio): Ratio = x + y | |
def minus(x: Ratio, y: Ratio): Ratio = x - y | |
def times(x: Ratio, y: Ratio): Ratio = x * y | |
def div(x: Ratio, y: Ratio): Ratio = x / y | |
def negate(x: Ratio): Ratio = -x | |
def fromInt(x: Int): Ratio = Ratio(x) | |
def toInt(x: Ratio): Int = x.bigdecValue.toInt | |
def toLong(x: Ratio): Long = x.bigdecValue.toLong | |
def toFloat(x: Ratio): Float = x.bigdecValue.toFloat | |
def toDouble(x: Ratio): Double = x.bigdecValue.toDouble | |
} | |
trait RatioOrdering extends Ordering[Ratio] { | |
def compare(a: Ratio, b: Ratio) = a.bigdecValue compare b.bigdecValue | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment