Skip to content

Commit a652b61

Browse files
committed
1 parent d54ae94 commit a652b61

7 files changed

Lines changed: 242 additions & 9 deletions

File tree

algebra-core/src/main/scala/algebra/instances/bigInt.scala

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ trait BigIntInstances extends cats.kernel.instances.BigIntInstances {
1010
new BigIntAlgebra
1111
}
1212

13-
class BigIntAlgebra extends CommutativeRing[BigInt] with Serializable {
13+
class BigIntAlgebra extends EuclideanRing[BigInt] with Serializable {
1414

1515
val zero: BigInt = BigInt(0)
1616
val one: BigInt = BigInt(1)
@@ -25,4 +25,32 @@ class BigIntAlgebra extends CommutativeRing[BigInt] with Serializable {
2525

2626
override def fromInt(n: Int): BigInt = BigInt(n)
2727
override def fromBigInt(n: BigInt): BigInt = n
28+
29+
override def lcm(a: BigInt, b: BigInt)(implicit ev: Eq[BigInt]): BigInt =
30+
if (a.signum == 0 || b.signum == 0) zero else (a / a.gcd(b)) * b
31+
override def gcd(a: BigInt, b: BigInt)(implicit ev: Eq[BigInt]): BigInt = a.gcd(b)
32+
33+
def euclideanFunction(a: BigInt): BigInt = a.abs
34+
35+
override def equotmod(a: BigInt, b: BigInt): (BigInt, BigInt) = {
36+
val (qt, rt) = a /% b // truncated quotient and remainder
37+
if (rt.signum >= 0) (qt, rt)
38+
else if (b.signum > 0) (qt - 1, rt + b)
39+
else (qt + 1, rt - b)
40+
}
41+
42+
def equot(a: BigInt, b: BigInt): BigInt = {
43+
val (qt, rt) = a /% b // truncated quotient and remainder
44+
if (rt.signum >= 0) qt
45+
else if (b.signum > 0) qt - 1
46+
else qt + 1
47+
}
48+
49+
def emod(a: BigInt, b: BigInt): BigInt = {
50+
val rt = a % b // truncated remainder
51+
if (rt.signum >= 0) rt
52+
else if (b > 0) rt + b
53+
else rt - b
54+
}
55+
2856
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package algebra
2+
package ring
3+
4+
import scala.{specialized => sp}
5+
6+
trait DivisionRing[@sp(Byte, Short, Int, Long, Float, Double) A] extends Any with Ring[A] with MultiplicativeGroup[A] {
7+
self =>
8+
9+
def fromDouble(a: Double): A = Field.defaultFromDouble[A](a)(self, self)
10+
11+
}
12+
13+
trait DivisionRingFunctions[F[T] <: DivisionRing[T]] extends RingFunctions[F] with MultiplicativeGroupFunctions[F] {
14+
def fromDouble[@sp(Int, Long, Float, Double) A](n: Double)(implicit ev: F[A]): A =
15+
ev.fromDouble(n)
16+
}
17+
18+
object DivisionRing extends DivisionRingFunctions[DivisionRing] {
19+
20+
@inline final def apply[A](implicit f: DivisionRing[A]): DivisionRing[A] = f
21+
22+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package algebra
2+
package ring
3+
4+
import scala.annotation.tailrec
5+
import scala.{specialized => sp}
6+
7+
/**
8+
* EuclideanRing implements a Euclidean domain.
9+
*
10+
* The formal definition says that every euclidean domain A has (at
11+
* least one) euclidean function f: A -> N (the natural numbers) where:
12+
*
13+
* (for every x and non-zero y) x = yq + r, and r = 0 or f(r) < f(y).
14+
*
15+
* This generalizes the Euclidean division of integers, where f represents
16+
* a measure of length (or absolute value), and the previous equation
17+
* represents finding the quotient and remainder of x and y. So:
18+
*
19+
* quot(x, y) = q
20+
* mod(x, y) = r
21+
*/
22+
trait EuclideanRing[@sp(Int, Long, Float, Double) A] extends Any with GCDRing[A] { self =>
23+
def euclideanFunction(a: A): BigInt
24+
def equot(a: A, b: A): A
25+
def emod(a: A, b: A): A
26+
def equotmod(a: A, b: A): (A, A) = (equot(a, b), emod(a, b))
27+
def gcd(a: A, b: A)(implicit ev: Eq[A]): A =
28+
EuclideanRing.euclid(a, b)(ev, self)
29+
def lcm(a: A, b: A)(implicit ev: Eq[A]): A =
30+
if (isZero(a) || isZero(b)) zero else times(equot(a, gcd(a, b)), b)
31+
}
32+
33+
trait EuclideanRingFunctions[R[T] <: EuclideanRing[T]] extends GCDRingFunctions[R] {
34+
def euclideanFunction[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: R[A]): BigInt =
35+
ev.euclideanFunction(a)
36+
def equot[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A]): A =
37+
ev.equot(a, b)
38+
def emod[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A]): A =
39+
ev.emod(a, b)
40+
def equotmod[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A]): (A, A) =
41+
ev.equotmod(a, b)
42+
}
43+
44+
object EuclideanRing extends EuclideanRingFunctions[EuclideanRing] {
45+
46+
@inline final def apply[A](implicit e: EuclideanRing[A]): EuclideanRing[A] = e
47+
48+
/**
49+
* Simple implementation of Euclid's algorithm for gcd
50+
*/
51+
@tailrec final def euclid[@sp(Int, Long, Float, Double) A: Eq: EuclideanRing](a: A, b: A): A = {
52+
if (EuclideanRing[A].isZero(b)) a else euclid(b, EuclideanRing[A].emod(a, b))
53+
}
54+
55+
}

algebra-core/src/main/scala/algebra/ring/Field.scala

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,21 @@ package ring
33

44
import scala.{specialized => sp}
55

6-
trait Field[@sp(Int, Long, Float, Double) A]
7-
extends Any
8-
with CommutativeRing[A]
9-
with MultiplicativeCommutativeGroup[A] { self =>
6+
trait Field[@sp(Int, Long, Float, Double) A] extends Any with EuclideanRing[A] with MultiplicativeCommutativeGroup[A] {
7+
self =>
8+
9+
// default implementations for GCD
10+
11+
override def gcd(a: A, b: A)(implicit eqA: Eq[A]): A =
12+
if (isZero(a) && isZero(b)) zero else one
13+
override def lcm(a: A, b: A)(implicit eqA: Eq[A]): A = times(a, b)
14+
15+
// default implementations for Euclidean division in a field (as every nonzero element is a unit!)
16+
17+
def euclideanFunction(a: A): BigInt = BigInt(0)
18+
def equot(a: A, b: A): A = div(a, b)
19+
def emod(a: A, b: A): A = zero
20+
override def equotmod(a: A, b: A): (A, A) = (div(a, b), zero)
1021

1122
/**
1223
* This is implemented in terms of basic Field ops. However, this is
@@ -20,7 +31,7 @@ trait Field[@sp(Int, Long, Float, Double) A]
2031

2132
}
2233

23-
trait FieldFunctions[F[T] <: Field[T]] extends RingFunctions[F] with MultiplicativeGroupFunctions[F] {
34+
trait FieldFunctions[F[T] <: Field[T]] extends EuclideanRingFunctions[F] with MultiplicativeGroupFunctions[F] {
2435
def fromDouble[@sp(Int, Long, Float, Double) A](n: Double)(implicit ev: F[A]): A =
2536
ev.fromDouble(n)
2637
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package algebra
2+
package ring
3+
4+
import scala.{specialized => sp}
5+
6+
/**
7+
* GCDRing implements a GCD ring.
8+
*
9+
* For two elements x and y in a GCD ring, we can choose two elements d and m
10+
* such that:
11+
*
12+
* d = gcd(x, y)
13+
* m = lcm(x, y)
14+
*
15+
* d * m = x * y
16+
*
17+
* Additionally, we require:
18+
*
19+
* gcd(0, 0) = 0
20+
* lcm(x, 0) = lcm(0, x) = 0
21+
*
22+
* and commutativity:
23+
*
24+
* gcd(x, y) = gcd(y, x)
25+
* lcm(x, y) = lcm(y, x)
26+
*/
27+
trait GCDRing[@sp(Int, Long, Float, Double) A] extends Any with CommutativeRing[A] {
28+
def gcd(a: A, b: A)(implicit ev: Eq[A]): A
29+
def lcm(a: A, b: A)(implicit ev: Eq[A]): A
30+
}
31+
32+
trait GCDRingFunctions[R[T] <: GCDRing[T]] extends RingFunctions[R] {
33+
def gcd[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A], eqA: Eq[A]): A =
34+
ev.gcd(a, b)(eqA)
35+
def lcm[@sp(Int, Long, Float, Double) A](a: A, b: A)(implicit ev: R[A], eqA: Eq[A]): A =
36+
ev.lcm(a, b)(eqA)
37+
}
38+
39+
object GCDRing extends GCDRingFunctions[GCDRing] {
40+
@inline final def apply[A](implicit ev: GCDRing[A]): GCDRing[A] = ev
41+
}

algebra-laws/shared/src/main/scala/algebra/laws/RingLaws.scala

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,57 @@ trait RingLaws[A] extends GroupLaws[A] { self =>
205205
parents = Seq(ring, commutativeRig, commutativeRng)
206206
)
207207

208+
def gcdRing(implicit A: GCDRing[A]) = RingProperties.fromParent(
209+
name = "gcd domain",
210+
parent = commutativeRing,
211+
"gcd/lcm" -> forAll { (x: A, y: A) =>
212+
val d = A.gcd(x, y)
213+
val m = A.lcm(x, y)
214+
A.times(x, y) ?== A.times(d, m)
215+
},
216+
"gcd is commutative" -> forAll { (x: A, y: A) =>
217+
A.gcd(x, y) ?== A.gcd(y, x)
218+
},
219+
"lcm is commutative" -> forAll { (x: A, y: A) =>
220+
A.lcm(x, y) ?== A.lcm(y, x)
221+
},
222+
"gcd(0, 0)" -> (A.gcd(A.zero, A.zero) ?== A.zero),
223+
"lcm(0, 0) === 0" -> (A.lcm(A.zero, A.zero) ?== A.zero),
224+
"lcm(x, 0) === 0" -> forAll { (x: A) => A.lcm(x, A.zero) ?== A.zero }
225+
)
226+
227+
def euclideanRing(implicit A: EuclideanRing[A]) = RingProperties.fromParent(
228+
name = "euclidean ring",
229+
parent = gcdRing,
230+
"euclidean division rule" -> forAll { (x: A, y: A) =>
231+
pred(y) ==> {
232+
val (q, r) = A.equotmod(x, y)
233+
x ?== A.plus(A.times(y, q), r)
234+
}
235+
},
236+
"equot" -> forAll { (x: A, y: A) =>
237+
pred(y) ==> {
238+
A.equotmod(x, y)._1 ?== A.equot(x, y)
239+
}
240+
},
241+
"emod" -> forAll { (x: A, y: A) =>
242+
pred(y) ==> {
243+
A.equotmod(x, y)._2 ?== A.emod(x, y)
244+
}
245+
},
246+
"euclidean function" -> forAll { (x: A, y: A) =>
247+
pred(y) ==> {
248+
val (_, r) = A.equotmod(x, y)
249+
A.isZero(r) || (A.euclideanFunction(r) < A.euclideanFunction(y))
250+
}
251+
},
252+
"submultiplicative function" -> forAll { (x: A, y: A) =>
253+
(pred(x) && pred(y)) ==> {
254+
A.euclideanFunction(x) <= A.euclideanFunction(A.times(x, y))
255+
}
256+
}
257+
)
258+
208259
// boolean rings
209260

210261
def boolRng(implicit A: BoolRng[A]) = RingProperties.fromParent(
@@ -231,6 +282,31 @@ trait RingLaws[A] extends GroupLaws[A] { self =>
231282
// zero * x == x * zero hold.
232283
// Luckily, these follow from the other field and group axioms.
233284
def field(implicit A: Field[A]) = new RingProperties(
285+
name = "field",
286+
al = additiveCommutativeGroup,
287+
ml = multiplicativeCommutativeGroup,
288+
parents = Seq(euclideanRing),
289+
"fromDouble" -> forAll { (n: Double) =>
290+
if (Platform.isJvm) {
291+
// TODO: BigDecimal(n) is busted in scalajs, so we skip this test.
292+
val bd = new java.math.BigDecimal(n)
293+
val unscaledValue = new BigInt(bd.unscaledValue)
294+
val expected =
295+
if (bd.scale > 0) {
296+
A.div(A.fromBigInt(unscaledValue), A.fromBigInt(BigInt(10).pow(bd.scale)))
297+
} else {
298+
A.fromBigInt(unscaledValue * BigInt(10).pow(-bd.scale))
299+
}
300+
Field.fromDouble[A](n) ?== expected
301+
} else {
302+
Prop(true)
303+
}
304+
}
305+
)
306+
307+
// Approximate fields such a Float or Double, even through filtered using FPFilter, do not work well with
308+
// Euclidean ring tests
309+
def approxField(implicit A: Field[A]) = new RingProperties(
234310
name = "field",
235311
al = additiveCommutativeGroup,
236312
ml = multiplicativeCommutativeGroup,

algebra-laws/shared/src/test/scala/algebra/laws/LawTests.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,10 @@ class LawTests extends munit.DisciplineSuite {
123123
checkAll("Long", RingLaws[Long].commutativeRing)
124124
checkAll("Long", LatticeLaws[Long].boundedDistributiveLattice)
125125

126-
checkAll("BigInt", RingLaws[BigInt].commutativeRing)
126+
checkAll("BigInt", RingLaws[BigInt].euclideanRing)
127127

128-
checkAll("FPApprox[Float]", RingLaws[FPApprox[Float]].field)
129-
checkAll("FPApprox[Double]", RingLaws[FPApprox[Double]].field)
128+
checkAll("FPApprox[Float]", RingLaws[FPApprox[Float]].approxField)
129+
checkAll("FPApprox[Double]", RingLaws[FPApprox[Double]].approxField)
130130

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

0 commit comments

Comments
 (0)