Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion algebra-core/src/main/scala/cats/algebra/instances/bigInt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ trait BigIntInstances extends cats.kernel.instances.BigIntInstances {
new BigIntAlgebra
}

class BigIntAlgebra extends CommutativeRing[BigInt] with Serializable {
class BigIntAlgebra extends EuclideanRing[BigInt] with Serializable {

val zero: BigInt = BigInt(0)
val one: BigInt = BigInt(1)
Expand All @@ -26,4 +26,32 @@ class BigIntAlgebra extends CommutativeRing[BigInt] with Serializable {

override def fromInt(n: Int): BigInt = BigInt(n)
override def fromBigInt(n: BigInt): BigInt = n

override def lcm(a: BigInt, b: BigInt)(implicit ev: Eq[BigInt]): BigInt =
if (a.signum == 0 || b.signum == 0) zero else (a / a.gcd(b)) * b
override def gcd(a: BigInt, b: BigInt)(implicit ev: Eq[BigInt]): BigInt = a.gcd(b)

def euclideanFunction(a: BigInt): BigInt = a.abs

override def equotmod(a: BigInt, b: BigInt): (BigInt, BigInt) = {
val (qt, rt) = a /% b // truncated quotient and remainder
if (rt.signum >= 0) (qt, rt)
else if (b.signum > 0) (qt - 1, rt + b)
else (qt + 1, rt - b)
}

def equot(a: BigInt, b: BigInt): BigInt = {
val (qt, rt) = a /% b // truncated quotient and remainder
if (rt.signum >= 0) qt
else if (b.signum > 0) qt - 1
else qt + 1
}

def emod(a: BigInt, b: BigInt): BigInt = {
val rt = a % b // truncated remainder
if (rt.signum >= 0) rt
else if (b > 0) rt + b
else rt - b
}

}
23 changes: 23 additions & 0 deletions algebra-core/src/main/scala/cats/algebra/ring/DivisionRing.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package cats
package algebra
package ring

import scala.{specialized => sp}

trait DivisionRing[@sp(Byte, Short, Int, Long, Float, Double) A] extends Any with Ring[A] with MultiplicativeGroup[A] {
self =>

def fromDouble(a: Double): A = Field.defaultFromDouble[A](a)(self, self)

}

trait DivisionRingFunctions[F[T] <: DivisionRing[T]] extends RingFunctions[F] with MultiplicativeGroupFunctions[F] {
def fromDouble[@sp(Int, Long, Float, Double) A](n: Double)(implicit ev: F[A]): A =
ev.fromDouble(n)
}

object DivisionRing extends DivisionRingFunctions[DivisionRing] {

@inline final def apply[A](implicit f: DivisionRing[A]): DivisionRing[A] = f

}
60 changes: 60 additions & 0 deletions algebra-core/src/main/scala/cats/algebra/ring/EuclideanRing.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package cats
package algebra
package ring

import scala.annotation.tailrec
import scala.{specialized => sp}

/**
* EuclideanRing implements a Euclidean domain.
*
* The formal definition says that every euclidean domain A has (at
* least one) euclidean function f: A -> N (the natural numbers) where:
*
* (for every x and non-zero y) x = yq + r, and r = 0 or f(r) < f(y).
*
* This generalizes the Euclidean division of integers, where f represents
* a measure of length (or absolute value), and the previous equation
* represents finding the quotient and remainder of x and y. So:
*
* quot(x, y) = q
* mod(x, y) = r
*/
trait EuclideanRing[@sp(Int, Long, Float, Double) A] extends Any with GCDRing[A] { self =>
def euclideanFunction(a: A): BigInt
def equot(a: A, b: A): A
def emod(a: A, b: A): A
def equotmod(a: A, b: A): (A, A) = (equot(a, b), emod(a, b))
def gcd(a: A, b: A)(implicit ev: Eq[A]): A =
EuclideanRing.euclid(a, b)(ev, self)
def lcm(a: A, b: A)(implicit ev: Eq[A]): A =
if (isZero(a) || isZero(b)) zero else times(equot(a, gcd(a, b)), b)
// def xgcd(a: A, b: A)(implicit ev: Eq[A]): (A, A, A) =
}

trait EuclideanRingFunctions[R[T] <: EuclideanRing[T]] extends GCDRingFunctions[R] {
def euclideanFunction[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: R[A]): BigInt =
ev.euclideanFunction(a)
def equot[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A]): A =
ev.equot(a, b)
def emod[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A]): A =
ev.emod(a, b)
def equotmod[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A]): (A, A) =
ev.equotmod(a, b)
}

object EuclideanRing extends EuclideanRingFunctions[EuclideanRing] {

@inline final def apply[A](implicit e: EuclideanRing[A]): EuclideanRing[A] = e

/**
* Simple implementation of Euclid's algorithm for gcd
*/
@tailrec final def euclid[@sp(Int, Long, Float, Double) A: Eq: EuclideanRing](a: A, b: A): A = {
if (EuclideanRing[A].isZero(b)) a else euclid(b, EuclideanRing[A].emod(a, b))
}

/* @tailrec final def extendedEuclid[@sp(Int, Long, Float, Double) A: Eq: EuclideanRing](a: A, b: A): (A, A, A) = {
if (EuclideanRing[A].isZero(b)) a else euclid(b, EuclideanRing[A].emod(a, b))*/
Comment thread
armanbilge marked this conversation as resolved.
Outdated

}
17 changes: 15 additions & 2 deletions algebra-core/src/main/scala/cats/algebra/ring/Field.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,22 @@ import scala.{specialized => sp}

trait Field[@sp(Int, Long, Float, Double) A]
extends Any
with CommutativeRing[A]
with EuclideanRing[A]
with MultiplicativeCommutativeGroup[A] { self =>

// default implementations for GCD

override def gcd(a: A, b: A)(implicit eqA: Eq[A]): A =
if (isZero(a) && isZero(b)) zero else one
override def lcm(a: A, b: A)(implicit eqA: Eq[A]): A = times(a, b)

// default implementations for Euclidean division in a field (as every nonzero element is a unit!)

def euclideanFunction(a: A): BigInt = BigInt(0)
def equot(a: A, b: A): A = div(a, b)
def emod(a: A, b: A): A = zero
override def equotmod(a: A, b: A): (A, A) = (div(a, b), zero)

/**
* This is implemented in terms of basic Field ops. However, this is
* probably significantly less efficient than can be done with a
Expand All @@ -21,7 +34,7 @@ trait Field[@sp(Int, Long, Float, Double) A]

}

trait FieldFunctions[F[T] <: Field[T]] extends RingFunctions[F] with MultiplicativeGroupFunctions[F] {
trait FieldFunctions[F[T] <: Field[T]] extends EuclideanRingFunctions[F] with MultiplicativeGroupFunctions[F] {
def fromDouble[@sp(Int, Long, Float, Double) A](n: Double)(implicit ev: F[A]): A =
ev.fromDouble(n)
}
Expand Down
42 changes: 42 additions & 0 deletions algebra-core/src/main/scala/cats/algebra/ring/GCDRing.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cats
package algebra
package ring

import scala.{specialized => sp}

/**
* GCDRing implements a GCD ring.
*
* For two elements x and y in a GCD ring, we can choose two elements d and m
* such that:
*
* d = gcd(x, y)
* m = lcm(x, y)
*
* d * m = x * y
*
* Additionally, we require:
*
* gcd(0, 0) = 0
* lcm(x, 0) = lcm(0, x) = 0
*
* and commutativity:
*
* gcd(x, y) = gcd(y, x)
* lcm(x, y) = lcm(y, x)
*/
trait GCDRing[@sp(Int, Long, Float, Double) A] extends Any with CommutativeRing[A] {
def gcd(a: A, b: A)(implicit ev: Eq[A]): A
def lcm(a: A, b: A)(implicit ev: Eq[A]): A
}

trait GCDRingFunctions[R[T] <: GCDRing[T]] extends RingFunctions[R] {
def gcd[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A], eqA: Eq[A]): A =
ev.gcd(a, b)(eqA)
def lcm[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A], eqA: Eq[A]): A =
ev.lcm(a, b)(eqA)
}

object GCDRing extends GCDRingFunctions[GCDRing] {
@inline final def apply[A](implicit ev: GCDRing[A]): GCDRing[A] = ev
}
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,57 @@ trait RingLaws[A] extends GroupLaws[A] { self =>
parents = Seq(ring, commutativeRig, commutativeRng)
)

def gcdRing(implicit A: GCDRing[A]) = RingProperties.fromParent(
name = "gcd domain",
parent = commutativeRing,
"gcd/lcm" -> forAll { (x: A, y: A) =>
val d = A.gcd(x, y)
val m = A.lcm(x, y)
A.times(x, y) ?== A.times(d, m)
},
"gcd is commutative" -> forAll { (x: A, y: A) =>
A.gcd(x, y) ?== A.gcd(y, x)
},
"lcm is commutative" -> forAll { (x: A, y: A) =>
A.lcm(x, y) ?== A.lcm(y, x)
},
"gcd(0, 0)" -> (A.gcd(A.zero, A.zero) ?== A.zero),
"lcm(0, 0) === 0" -> (A.lcm(A.zero, A.zero) ?== A.zero),
"lcm(x, 0) === 0" -> forAll { (x: A) => A.lcm(x, A.zero) ?== A.zero }
)

def euclideanRing(implicit A: EuclideanRing[A]) = RingProperties.fromParent(
name = "euclidean ring",
parent = gcdRing,
"euclidean division rule" -> forAll { (x: A, y: A) =>
pred(y) ==> {
val (q, r) = A.equotmod(x, y)
x ?== A.plus(A.times(y, q), r)
}
},
"equot" -> forAll { (x: A, y: A) =>
pred(y) ==> {
A.equotmod(x, y)._1 ?== A.equot(x, y)
}
},
"emod" -> forAll { (x: A, y: A) =>
pred(y) ==> {
A.equotmod(x, y)._2 ?== A.emod(x, y)
}
},
"euclidean function" -> forAll { (x: A, y: A) =>
pred(y) ==> {
val (_, r) = A.equotmod(x, y)
A.isZero(r) || (A.euclideanFunction(r) < A.euclideanFunction(y))
}
},
"submultiplicative function" -> forAll { (x: A, y: A) =>
(pred(x) && pred(y)) ==> {
A.euclideanFunction(x) <= A.euclideanFunction(A.times(x, y))
}
}
)

// boolean rings

def boolRng(implicit A: BoolRng[A]) = RingProperties.fromParent(
Expand All @@ -232,6 +283,31 @@ trait RingLaws[A] extends GroupLaws[A] { self =>
// zero * x == x * zero hold.
// Luckily, these follow from the other field and group axioms.
def field(implicit A: Field[A]) = new RingProperties(
name = "field",
al = additiveCommutativeGroup,
ml = multiplicativeCommutativeGroup,
parents = Seq(euclideanRing),
"fromDouble" -> forAll { (n: Double) =>
if (Platform.isJvm) {
// TODO: BigDecimal(n) is busted in scalajs, so we skip this test.
val bd = new java.math.BigDecimal(n)
val unscaledValue = new BigInt(bd.unscaledValue)
val expected =
if (bd.scale > 0) {
A.div(A.fromBigInt(unscaledValue), A.fromBigInt(BigInt(10).pow(bd.scale)))
} else {
A.fromBigInt(unscaledValue * BigInt(10).pow(-bd.scale))
}
Field.fromDouble[A](n) ?== expected
} else {
Prop(true)
}
}
)

// Approximate fields such a Float or Double, even through filtered using FPFilter, do not work well with
// Euclidean ring tests
def approxField(implicit A: Field[A]) = new RingProperties(
name = "field",
al = additiveCommutativeGroup,
ml = multiplicativeCommutativeGroup,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ class LawTests extends munit.DisciplineSuite {
checkAll("Long", RingLaws[Long].commutativeRing)
checkAll("Long", LatticeLaws[Long].boundedDistributiveLattice)

checkAll("BigInt", RingLaws[BigInt].commutativeRing)
checkAll("BigInt", RingLaws[BigInt].euclideanRing)

checkAll("FPApprox[Float]", RingLaws[FPApprox[Float]].field)
checkAll("FPApprox[Double]", RingLaws[FPApprox[Double]].field)
checkAll("FPApprox[Float]", RingLaws[FPApprox[Float]].approxField)
checkAll("FPApprox[Double]", RingLaws[FPApprox[Double]].approxField)

// let's limit our BigDecimal-related tests to the JVM for now.
if (Platform.isJvm) {
Expand Down