From 5c4d1700255cbcec967827334608752becb63a38 Mon Sep 17 00:00:00 2001 From: Arman Bilge Date: Sat, 19 Jun 2021 21:34:33 +0000 Subject: [PATCH] Add tests for Signed/TruncatedDivision Finishes merging https://github.com/typelevel/algebra/pull/248 --- .../main/scala/algebra/laws/OrderLaws.scala | 104 ++++++++++++++++++ .../test/scala/algebra/laws/FPApprox.scala | 2 +- .../test/scala/algebra/laws/LawTests.scala | 2 + 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/algebra-laws/shared/src/main/scala/algebra/laws/OrderLaws.scala b/algebra-laws/shared/src/main/scala/algebra/laws/OrderLaws.scala index d9db4cda92..5a2aecae10 100644 --- a/algebra-laws/shared/src/main/scala/algebra/laws/OrderLaws.scala +++ b/algebra-laws/shared/src/main/scala/algebra/laws/OrderLaws.scala @@ -8,6 +8,11 @@ import org.scalacheck.{Arbitrary, Cogen, Prop} import org.scalacheck.Prop._ import cats.kernel.instances.all._ +import algebra.ring.Signed +import algebra.ring.CommutativeRing +import algebra.ring.TruncatedDivision +import algebra.ring.AdditiveCommutativeGroup +import algebra.ring.GCDRing @deprecated("Provided by cats.kernel.laws", since = "2.7.0") object OrderLaws { @@ -114,6 +119,105 @@ trait OrderLaws[A] extends Laws { } ) + def signed(implicit A: Signed[A]) = new OrderProperties( + name = "signed", + parent = Some(order(A.order)), + "abs non-negative" -> forAll((x: A) => A.sign(A.abs(x)) != Signed.Negative), + "signum returns -1/0/1" -> forAll((x: A) => A.signum(A.abs(x)) <= 1), + "signum is sign.toInt" -> forAll((x: A) => A.signum(x) == A.sign(x).toInt), + "ordered group" -> forAll { (x: A, y: A, z: A) => + A.order.lteqv(x, y) ==> A.order.lteqv(A.additiveCommutativeMonoid.plus(x, z), + A.additiveCommutativeMonoid.plus(y, z) + ) + }, + "triangle inequality" -> forAll { (x: A, y: A) => + A.order.lteqv(A.abs(A.additiveCommutativeMonoid.plus(x, y)), A.additiveCommutativeMonoid.plus(A.abs(x), A.abs(y))) + } + ) + + def signedAdditiveCommutativeGroup(implicit signedA: Signed[A], A: AdditiveCommutativeGroup[A]) = new DefaultRuleSet( + name = "signedAdditiveAbGroup", + parent = Some(signed), + "abs(x) equals abs(-x)" -> forAll { (x: A) => + signedA.abs(x) ?== signedA.abs(A.negate(x)) + } + ) + + // more a convention: as GCD is defined up to a unit, so up to a sign, + // on an ordered GCD ring we require gcd(x, y) >= 0, which is the common + // behavior of computer algebra systems + def signedGCDRing(implicit signedA: Signed[A], A: GCDRing[A]) = new DefaultRuleSet( + name = "signedGCDRing", + parent = Some(signedAdditiveCommutativeGroup), + "gcd(x, y) >= 0" -> forAll { (x: A, y: A) => + signedA.isSignNonNegative(A.gcd(x, y)) + }, + "gcd(x, 0) === abs(x)" -> forAll { (x: A) => + A.gcd(x, A.zero) ?== signedA.abs(x) + } + ) + + def truncatedDivision(implicit ring: CommutativeRing[A], A: TruncatedDivision[A]) = new DefaultRuleSet( + name = "truncatedDivision", + parent = Some(signed), + "division rule (tquotmod)" -> forAll { (x: A, y: A) => + A.isSignNonZero(y) ==> { + val (q, r) = A.tquotmod(x, y) + x ?== ring.plus(ring.times(y, q), r) + } + }, + "division rule (fquotmod)" -> forAll { (x: A, y: A) => + A.isSignNonZero(y) ==> { + val (q, r) = A.fquotmod(x, y) + x ?== ring.plus(ring.times(y, q), r) + } + }, + "|r| < |y| (tmod)" -> forAll { (x: A, y: A) => + A.isSignNonZero(y) ==> { + val r = A.tmod(x, y) + A.order.lt(A.abs(r), A.abs(y)) + } + }, + "|r| < |y| (fmod)" -> forAll { (x: A, y: A) => + A.isSignNonZero(y) ==> { + val r = A.fmod(x, y) + A.order.lt(A.abs(r), A.abs(y)) + } + }, + "r = 0 or sign(r) = sign(x) (tmod)" -> forAll { (x: A, y: A) => + A.isSignNonZero(y) ==> { + val r = A.tmod(x, y) + A.isSignZero(r) || (A.sign(r) ?== A.sign(x)) + } + }, + "r = 0 or sign(r) = sign(y) (fmod)" -> forAll { (x: A, y: A) => + A.isSignNonZero(y) ==> { + val r = A.fmod(x, y) + A.isSignZero(r) || (A.sign(r) ?== A.sign(y)) + } + }, + "tquot" -> forAll { (x: A, y: A) => + A.isSignNonZero(y) ==> { + A.tquotmod(x, y)._1 ?== A.tquot(x, y) + } + }, + "tmod" -> forAll { (x: A, y: A) => + A.isSignNonZero(y) ==> { + A.tquotmod(x, y)._2 ?== A.tmod(x, y) + } + }, + "fquot" -> forAll { (x: A, y: A) => + A.isSignNonZero(y) ==> { + A.fquotmod(x, y)._1 ?== A.fquot(x, y) + } + }, + "fmod" -> forAll { (x: A, y: A) => + A.isSignNonZero(y) ==> { + A.fquotmod(x, y)._2 ?== A.fmod(x, y) + } + } + ) + class OrderProperties( name: String, parent: Option[RuleSet], diff --git a/algebra-laws/shared/src/test/scala/algebra/laws/FPApprox.scala b/algebra-laws/shared/src/test/scala/algebra/laws/FPApprox.scala index a21d564011..0cedcd686e 100644 --- a/algebra-laws/shared/src/test/scala/algebra/laws/FPApprox.scala +++ b/algebra-laws/shared/src/test/scala/algebra/laws/FPApprox.scala @@ -66,7 +66,7 @@ object FPApprox { def exact[A: Rng: Order](a: A): FPApprox[A] = FPApprox(a, abs(a), 0) def approx[A: Rng: Order](a: A): FPApprox[A] = FPApprox(a, abs(a), 1) - trait Epsilon[A] { + trait Epsilon[A] extends Serializable { def minValue: A def epsilon: A def isFinite(a: A): Boolean diff --git a/algebra-laws/shared/src/test/scala/algebra/laws/LawTests.scala b/algebra-laws/shared/src/test/scala/algebra/laws/LawTests.scala index 0e35b30c5d..5da2116154 100644 --- a/algebra-laws/shared/src/test/scala/algebra/laws/LawTests.scala +++ b/algebra-laws/shared/src/test/scala/algebra/laws/LawTests.scala @@ -123,7 +123,9 @@ class LawTests extends munit.DisciplineSuite { checkAll("Long", RingLaws[Long].commutativeRing) checkAll("Long", LatticeLaws[Long].boundedDistributiveLattice) + checkAll("BigInt", OrderLaws[BigInt].truncatedDivision) checkAll("BigInt", RingLaws[BigInt].euclideanRing) + checkAll("BigInt", OrderLaws[BigInt].signedGCDRing) checkAll("FPApprox[Float]", RingLaws[FPApprox[Float]].approxField) checkAll("FPApprox[Double]", RingLaws[FPApprox[Double]].approxField)