Skip to content

Commit 51530cb

Browse files
committed
Switch to Euclidean division for Int, resolves #161
Also provide `quot` and `rem`, like Haskell does, for users who do want truncating division - the one which matches what JS does. I've temporarily exported `intDiv` and `intMod` so that I can use those in the tests and the compiler won't 'inline' different definitions of them; we'll want to modify the compiler to change this before merging.
1 parent 3cbf8d3 commit 51530cb

File tree

3 files changed

+127
-0
lines changed

3 files changed

+127
-0
lines changed

src/Data/EuclideanRing.js

+15
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,29 @@ exports.intDegree = function (x) {
44
return Math.min(Math.abs(x), 2147483647);
55
};
66

7+
// See the Euclidean definition in
8+
// https://en.m.wikipedia.org/wiki/Modulo_operation.
79
exports.intDiv = function (x) {
10+
return function (y) {
11+
return Math.sign(y) * Math.floor(x / Math.abs(y));
12+
};
13+
};
14+
15+
exports.quot = function (x) {
816
return function (y) {
917
/* jshint bitwise: false */
1018
return x / y | 0;
1119
};
1220
};
1321

1422
exports.intMod = function (x) {
23+
return function (y) {
24+
var yy = Math.abs(y)
25+
return ((x % yy) + yy) % yy;
26+
};
27+
};
28+
29+
exports.rem = function (x) {
1530
return function (y) {
1631
return x % y;
1732
};

src/Data/EuclideanRing.purs

+58
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ module Data.EuclideanRing
22
( class EuclideanRing, degree, div, mod, (/)
33
, gcd
44
, lcm
5+
, quot
6+
, rem
57
, module Data.CommutativeRing
68
, module Data.Ring
79
, module Data.Semiring
10+
, intDiv
11+
, intMod
812
) where
913

1014
import Data.BooleanAlgebra ((||))
@@ -41,6 +45,25 @@ import Data.Semiring (class Semiring, add, mul, one, zero, (*), (+))
4145
-- | for `degree` is simply `const 1`. In fact, unless there's a specific
4246
-- | reason not to, `Field` types should normally use this definition of
4347
-- | `degree`.
48+
-- |
49+
-- | The `EuclideanRing Int` instance is one of the most commonly used
50+
-- | `EuclideanRing` instances and deserves a little more discussion. In
51+
-- | particular, there are a few different sensible law-abiding implementations
52+
-- | to choose from, with slightly different behaviour in the presence of
53+
-- | negative dividends or divisors. The most common definitions are "truncating"
54+
-- | division, where the result of `a / b` is rounded towards 0, and "Knuthian"
55+
-- | or "flooring" division, where the result of `a / b` is rounded towards
56+
-- | negative infinity. A slightly less common, but arguably more useful, option
57+
-- | is "Euclidean" division, which is defined so as to ensure that ``a `mod` b``
58+
-- | is always nonnegative. With Euclidean division, `a / b` rounds towards
59+
-- | negative infinity if the divisor is positive, and towards positive infinity
60+
-- | if the divisor is negative. Note that all three definitions are identical if
61+
-- | we restrict our attention to nonnegative dividends and divisors.
62+
63+
-- | In versions 1.x, 2.x, and 3.x of the Prelude, the `EuclideanRing Int`
64+
-- | instance used truncating division. As of 4.x, the `EuclideanRing Int`
65+
-- | instance uses Euclidean division. Additional functions `quot` and `rem` are
66+
-- | supplied if truncating division is desired.
4467
class CommutativeRing a <= EuclideanRing a where
4568
degree :: a -> Int
4669
div :: a -> a -> a
@@ -77,3 +100,38 @@ lcm a b =
77100
if a == zero || b == zero
78101
then zero
79102
else a * b / gcd a b
103+
104+
-- | The `quot` function provides _truncating_ integer division (see the
105+
-- | documentation for the `EuclideanRing` class). It is identical to `div` in
106+
-- | the `EuclideanRing Int` instance if the dividend is positive, but will be
107+
-- | slightly different if the dividend is negative. For example:
108+
-- |
109+
-- | ```purescript
110+
-- | div 2 3 == 0
111+
-- | quot 2 3 == 0
112+
-- |
113+
-- | div (-2) 3 == (-1)
114+
-- | quot (-2) 3 == 0
115+
-- |
116+
-- | div 2 (-3) == 0
117+
-- | quot 2 (-3) == 0
118+
-- | ```
119+
foreign import quot :: Int -> Int -> Int
120+
121+
-- | The `rem` function provides the remainder after _truncating_ integer
122+
-- | division (see the documentation for the `EuclideanRing` class). It is
123+
-- | identical to `mod` in the `EuclideanRing Int` instance if the dividend is
124+
-- | positive, but will be slightly different if the dividend is negative. For
125+
-- | example:
126+
-- |
127+
-- | ```purescript
128+
-- | mod 2 3 == 2
129+
-- | rem 2 3 == 2
130+
-- |
131+
-- | mod (-2) 3 == 1
132+
-- | rem (-2) 3 == (-2)
133+
-- |
134+
-- | mod 2 (-3) == 2
135+
-- | rem 2 (-3) == 2
136+
-- | ```
137+
foreign import rem :: Int -> Int -> Int

test/Test/Main.purs

+54
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
module Test.Main where
22

33
import Prelude
4+
import Data.EuclideanRing (intDiv, intMod)
5+
import Data.Ord (abs)
46

57
type AlmostEff = Unit -> Unit
68

@@ -9,6 +11,8 @@ main = do
911
testNumberShow show
1012
testOrderings
1113
testOrdUtils
14+
testIntDivMod
15+
testIntQuotRem
1216
testIntDegree
1317

1418
foreign import testNumberShow :: (Number -> String) -> AlmostEff
@@ -82,6 +86,56 @@ testOrdUtils = do
8286
assert "5 should be between 0 and 10" $ between 0 10 5 == true
8387
assert "15 should not be between 0 10" $ between 0 10 15 == false
8488

89+
testIntDivMod :: AlmostEff
90+
testIntDivMod = do
91+
-- Check when dividend goes into divisor exactly
92+
go 8 2
93+
go (-8) 2
94+
go 8 (-2)
95+
go (-8) (-2)
96+
97+
-- Check when dividend does not go into divisor exactly
98+
go 2 3
99+
go (-2) 3
100+
go 2 (-3)
101+
go (-2) (-3)
102+
103+
where
104+
go a b =
105+
let
106+
q = intDiv a b
107+
r = intMod a b
108+
msg = show a <> " / " <> show b <> ": "
109+
in do
110+
assert (msg <> "Quotient/remainder law") $
111+
q * b + r == a
112+
assert (msg <> "Remainder should be between 0 and `abs b`, got: " <> show r) $
113+
0 <= r && r < abs b
114+
115+
testIntQuotRem :: AlmostEff
116+
testIntQuotRem = do
117+
-- Check when dividend goes into divisor exactly
118+
go 8 2
119+
go (-8) 2
120+
go 8 (-2)
121+
go (-8) (-2)
122+
123+
-- Check when dividend does not go into divisor exactly
124+
go 2 3
125+
go (-2) 3
126+
go 2 (-3)
127+
go (-2) (-3)
128+
129+
where
130+
go a b =
131+
let
132+
q = quot a b
133+
r = rem a b
134+
msg = show a <> " / " <> show b <> ": "
135+
in do
136+
assert (msg <> "Quotient/remainder law") $
137+
q * b + r == a
138+
85139
testIntDegree :: AlmostEff
86140
testIntDegree = do
87141
let bot = bottom :: Int

0 commit comments

Comments
 (0)