-
Notifications
You must be signed in to change notification settings - Fork 69
Adding GCD rings and Euclidean rings, along with instances for BigInt #246
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
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 | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
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 => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we have specialized on these types can we also add the implementations? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ... Float and Double are taken charge of by Field (euclidean division is trivial). For Byte/Short/Int/Long, the instances are not lawful as overflow is undefined behaviour territory (which was not the case for just We could:
What do you think? |
||
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) = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we remove this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. Or I'll implement the extended GCD algorithm. |
||
if (EuclideanRing[A].isZero(b)) a else euclid(b, EuclideanRing[A].emod(a, b))*/ | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
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 |
---|---|---|
|
@@ -201,6 +201,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) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't see this comment in trait. Could we add it so people get more intuition for the euclideanFunction? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! |
||
(pred(x) && pred(y)) ==> { | ||
A.euclideanFunction(x) <= A.euclideanFunction(A.times(x, y)) | ||
} | ||
} | ||
) | ||
|
||
// boolean rings | ||
|
||
def boolRng(implicit A: BoolRng[A]) = RingProperties.fromParent( | ||
|
@@ -227,6 +278,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, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any law on this? Seems odd to me a division ring only has this extra method. We don't have any other division like operation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Field has it too. I should have
fromDouble
in DivisionRing and makeField
inheritDivisionRing
.Basically, if you have a type
A
with a functionBigInt -> A
and division, you havefromDouble
.(Should algebra incorporate DivisionRing, or is it too specialized? Note that DivisionRing covers the quaternions)