Skip to content

Commit 8ffb393

Browse files
armanbilgepikinier20
authored andcommitted
Add Signed and TruncatedDivision typeclasses
Partially merges typelevel/algebra#247
1 parent e46e446 commit 8ffb393

File tree

3 files changed

+247
-2
lines changed

3 files changed

+247
-2
lines changed

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ import algebra.ring._
66
package object bigInt extends BigIntInstances
77

88
trait BigIntInstances extends cats.kernel.instances.BigIntInstances {
9-
implicit val bigIntAlgebra: BigIntAlgebra =
10-
new BigIntAlgebra
9+
implicit val bigIntAlgebra: BigIntAlgebra = new BigIntTruncatedDivison
10+
implicit def bigIntTruncatedDivision: TruncatedDivision[BigInt] =
11+
bigIntAlgebra.asInstanceOf[BigIntTruncatedDivison] // Bin-compat hack to avoid allocation
1112
}
1213

1314
class BigIntAlgebra extends EuclideanRing[BigInt] with Serializable {
@@ -54,3 +55,9 @@ class BigIntAlgebra extends EuclideanRing[BigInt] with Serializable {
5455
}
5556

5657
}
58+
59+
class BigIntTruncatedDivison extends BigIntAlgebra with TruncatedDivision.forCommutativeRing[BigInt] {
60+
override def tquot(x: BigInt, y: BigInt): BigInt = x / y
61+
override def tmod(x: BigInt, y: BigInt): BigInt = x % y
62+
override def order: Order[BigInt] = cats.kernel.instances.bigInt.catsKernelStdOrderForBigInt
63+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package algebra.ring
2+
3+
import algebra.{CommutativeMonoid, Eq, Order}
4+
5+
import scala.{specialized => sp}
6+
7+
/**
8+
* A trait that expresses the existence of signs and absolute values on linearly ordered additive commutative monoids
9+
* (i.e. types with addition and a zero).
10+
*
11+
* The following laws holds:
12+
*
13+
* (1) if `a <= b` then `a + c <= b + c` (linear order),
14+
* (2) `signum(x) = -1` if `x < 0`, `signum(x) = 1` if `x > 0`, `signum(x) = 0` otherwise,
15+
*
16+
* Negative elements only appear when the scalar is taken from a additive abelian group. Then:
17+
*
18+
* (3) `abs(x) = -x` if `x < 0`, or `x` otherwise,
19+
*
20+
* Laws (1) and (2) lead to the triange inequality:
21+
*
22+
* (4) `abs(a + b) <= abs(a) + abs(b)`
23+
*
24+
* Signed should never be extended in implementations, rather the [[Signed.forAdditiveCommutativeMonoid]] and
25+
* [[Signed.forAdditiveCommutativeGroup subtraits]].
26+
*
27+
* It's better to have the Signed hierarchy separate from the Ring/Order hierarchy, so that
28+
* we do not end up with duplicate implicits.
29+
*/
30+
trait Signed[@sp(Byte, Short, Int, Long, Float, Double) A] extends Any {
31+
32+
def additiveCommutativeMonoid: AdditiveCommutativeMonoid[A]
33+
def order: Order[A]
34+
35+
/**
36+
* Returns Zero if `a` is 0, Positive if `a` is positive, and Negative is `a` is negative.
37+
*/
38+
def sign(a: A): Signed.Sign = Signed.Sign(signum(a))
39+
40+
/**
41+
* Returns 0 if `a` is 0, 1 if `a` is positive, and -1 is `a` is negative.
42+
*/
43+
def signum(a: A): Int
44+
45+
/**
46+
* An idempotent function that ensures an object has a non-negative sign.
47+
*/
48+
def abs(a: A): A
49+
50+
def isSignZero(a: A): Boolean = signum(a) == 0
51+
def isSignPositive(a: A): Boolean = signum(a) > 0
52+
def isSignNegative(a: A): Boolean = signum(a) < 0
53+
54+
def isSignNonZero(a: A): Boolean = signum(a) != 0
55+
def isSignNonPositive(a: A): Boolean = signum(a) <= 0
56+
def isSignNonNegative(a: A): Boolean = signum(a) >= 0
57+
}
58+
59+
trait SignedFunctions[S[T] <: Signed[T]] extends cats.kernel.OrderFunctions[Order] {
60+
def sign[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: S[A]): Signed.Sign =
61+
ev.sign(a)
62+
def signum[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: S[A]): Int =
63+
ev.signum(a)
64+
def abs[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: S[A]): A =
65+
ev.abs(a)
66+
def isSignZero[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: S[A]): Boolean =
67+
ev.isSignZero(a)
68+
def isSignPositive[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: S[A]): Boolean =
69+
ev.isSignPositive(a)
70+
def isSignNegative[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: S[A]): Boolean =
71+
ev.isSignNegative(a)
72+
def isSignNonZero[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: S[A]): Boolean =
73+
ev.isSignNonZero(a)
74+
def isSignNonPositive[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: S[A]): Boolean =
75+
ev.isSignNonPositive(a)
76+
def isSignNonNegative[@sp(Int, Long, Float, Double) A](a: A)(implicit ev: S[A]): Boolean =
77+
ev.isSignNonNegative(a)
78+
}
79+
80+
object Signed extends SignedFunctions[Signed] {
81+
82+
/**
83+
* Signed implementation for additive commutative monoids
84+
*/
85+
trait forAdditiveCommutativeMonoid[A] extends Any with Signed[A] with AdditiveCommutativeMonoid[A] {
86+
final override def additiveCommutativeMonoid = this
87+
def signum(a: A): Int = {
88+
val c = order.compare(a, zero)
89+
if (c < 0) -1
90+
else if (c > 0) 1
91+
else 0
92+
}
93+
}
94+
95+
/**
96+
* Signed implementation for additive commutative groups
97+
*/
98+
trait forAdditiveCommutativeGroup[A]
99+
extends Any
100+
with forAdditiveCommutativeMonoid[A]
101+
with AdditiveCommutativeGroup[A] {
102+
def abs(a: A): A = if (order.compare(a, zero) < 0) negate(a) else a
103+
}
104+
105+
def apply[A](implicit s: Signed[A]): Signed[A] = s
106+
107+
/**
108+
* A simple ADT representing the `Sign` of an object.
109+
*/
110+
sealed abstract class Sign(val toInt: Int) {
111+
def unary_- : Sign = this match {
112+
case Positive => Negative
113+
case Negative => Positive
114+
case Zero => Zero
115+
}
116+
117+
def *(that: Sign): Sign = Sign(this.toInt * that.toInt)
118+
119+
def **(that: Int): Sign = this match {
120+
case Positive => Positive
121+
case Zero if that == 0 => Positive
122+
case Zero => Zero
123+
case Negative if (that % 2) == 0 => Positive
124+
case Negative => Negative
125+
}
126+
}
127+
128+
case object Zero extends Sign(0)
129+
case object Positive extends Sign(1)
130+
case object Negative extends Sign(-1)
131+
132+
object Sign {
133+
implicit def sign2int(s: Sign): Int = s.toInt
134+
135+
def apply(i: Int): Sign =
136+
if (i == 0) Zero else if (i > 0) Positive else Negative
137+
138+
private val instance: CommutativeMonoid[Sign] with MultiplicativeCommutativeMonoid[Sign] with Eq[Sign] =
139+
new CommutativeMonoid[Sign] with MultiplicativeCommutativeMonoid[Sign] with Eq[Sign] {
140+
def eqv(x: Sign, y: Sign): Boolean = x == y
141+
def empty: Sign = Positive
142+
def combine(x: Sign, y: Sign): Sign = x * y
143+
def one: Sign = Positive
144+
def times(x: Sign, y: Sign): Sign = x * y
145+
}
146+
147+
implicit final def signMultiplicativeMonoid: MultiplicativeCommutativeMonoid[Sign] = instance
148+
implicit final def signMonoid: CommutativeMonoid[Sign] = instance
149+
implicit final def signEq: Eq[Sign] = instance
150+
}
151+
152+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package algebra.ring
2+
3+
import scala.{specialized => sp}
4+
5+
/**
6+
* Division and modulus for computer scientists
7+
* taken from https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/divmodnote-letter.pdf
8+
*
9+
* For two numbers x (dividend) and y (divisor) on an ordered ring with y != 0,
10+
* there exists a pair of numbers q (quotient) and r (remainder)
11+
* such that these laws are satisfied:
12+
*
13+
* (1) q is an integer
14+
* (2) x = y * q + r (division rule)
15+
* (3) |r| < |y|,
16+
* (4t) r = 0 or sign(r) = sign(x),
17+
* (4f) r = 0 or sign(r) = sign(y).
18+
*
19+
* where sign is the sign function, and the absolute value
20+
* function |x| is defined as |x| = x if x >=0, and |x| = -x otherwise.
21+
*
22+
* We define functions tmod and tquot such that:
23+
* q = tquot(x, y) and r = tmod(x, y) obey rule (4t),
24+
* (which truncates effectively towards zero)
25+
* and functions fmod and fquot such that:
26+
* q = fquot(x, y) and r = fmod(x, y) obey rule (4f)
27+
* (which floors the quotient and effectively rounds towards negative infinity).
28+
*
29+
* Law (4t) corresponds to ISO C99 and Haskell's quot/rem.
30+
* Law (4f) is described by Knuth and used by Haskell,
31+
* and fmod corresponds to the REM function of the IEEE floating-point standard.
32+
*/
33+
trait TruncatedDivision[@sp(Byte, Short, Int, Long, Float, Double) A] extends Any with Signed[A] {
34+
def tquot(x: A, y: A): A
35+
def tmod(x: A, y: A): A
36+
def tquotmod(x: A, y: A): (A, A) = (tquot(x, y), tmod(x, y))
37+
38+
def fquot(x: A, y: A): A
39+
def fmod(x: A, y: A): A
40+
def fquotmod(x: A, y: A): (A, A) = (fquot(x, y), fmod(x, y))
41+
}
42+
43+
trait TruncatedDivisionFunctions[S[T] <: TruncatedDivision[T]] extends SignedFunctions[S] {
44+
def tquot[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: TruncatedDivision[A]): A =
45+
ev.tquot(x, y)
46+
def tmod[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: TruncatedDivision[A]): A =
47+
ev.tmod(x, y)
48+
def tquotmod[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: TruncatedDivision[A]): (A, A) =
49+
ev.tquotmod(x, y)
50+
def fquot[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: TruncatedDivision[A]): A =
51+
ev.fquot(x, y)
52+
def fmod[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: TruncatedDivision[A]): A =
53+
ev.fmod(x, y)
54+
def fquotmod[@sp(Int, Long, Float, Double) A](x: A, y: A)(implicit ev: TruncatedDivision[A]): (A, A) =
55+
ev.fquotmod(x, y)
56+
}
57+
58+
object TruncatedDivision extends TruncatedDivisionFunctions[TruncatedDivision] {
59+
trait forCommutativeRing[@sp(Byte, Short, Int, Long, Float, Double) A]
60+
extends Any
61+
with TruncatedDivision[A]
62+
with Signed.forAdditiveCommutativeGroup[A]
63+
with CommutativeRing[A] { self =>
64+
65+
def fmod(x: A, y: A): A = {
66+
val tm = tmod(x, y)
67+
if (signum(tm) == -signum(y)) plus(tm, y) else tm
68+
}
69+
70+
def fquot(x: A, y: A): A = {
71+
val (tq, tm) = tquotmod(x, y)
72+
if (signum(tm) == -signum(y)) minus(tq, one) else tq
73+
}
74+
75+
override def fquotmod(x: A, y: A): (A, A) = {
76+
val (tq, tm) = tquotmod(x, y)
77+
val signsDiffer = signum(tm) == -signum(y)
78+
val fq = if (signsDiffer) minus(tq, one) else tq
79+
val fm = if (signsDiffer) plus(tm, y) else tm
80+
(fq, fm)
81+
}
82+
83+
}
84+
85+
def apply[A](implicit ev: TruncatedDivision[A]): TruncatedDivision[A] = ev
86+
}

0 commit comments

Comments
 (0)