Skip to content

Commit

Permalink
Merge pull request #168 from purescript/euclidean-int
Browse files Browse the repository at this point in the history
Switch to Euclidean division for Int, resolves #161
  • Loading branch information
garyb authored Apr 22, 2018
2 parents 7e720c1 + 05a8540 commit dc51981
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 1 deletion.
15 changes: 15 additions & 0 deletions src/Data/EuclideanRing.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,29 @@ exports.intDegree = function (x) {
return Math.min(Math.abs(x), 2147483647);
};

// See the Euclidean definition in
// https://en.m.wikipedia.org/wiki/Modulo_operation.
exports.intDiv = function (x) {
return function (y) {
return y > 0 ? Math.floor(x / y) : -Math.floor(x / -y);
};
};

exports.quot = function (x) {
return function (y) {
/* jshint bitwise: false */
return x / y | 0;
};
};

exports.intMod = function (x) {
return function (y) {
var yy = Math.abs(y);
return ((x % yy) + yy) % yy;
};
};

exports.rem = function (x) {
return function (y) {
return x % y;
};
Expand Down
58 changes: 58 additions & 0 deletions src/Data/EuclideanRing.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ module Data.EuclideanRing
( class EuclideanRing, degree, div, mod, (/)
, gcd
, lcm
, quot
, rem
, module Data.CommutativeRing
, module Data.Ring
, module Data.Semiring
, intDiv
, intMod
) where

import Data.BooleanAlgebra ((||))
Expand Down Expand Up @@ -41,6 +45,25 @@ import Data.Semiring (class Semiring, add, mul, one, zero, (*), (+))
-- | for `degree` is simply `const 1`. In fact, unless there's a specific
-- | reason not to, `Field` types should normally use this definition of
-- | `degree`.
-- |
-- | The `EuclideanRing Int` instance is one of the most commonly used
-- | `EuclideanRing` instances and deserves a little more discussion. In
-- | particular, there are a few different sensible law-abiding implementations
-- | to choose from, with slightly different behaviour in the presence of
-- | negative dividends or divisors. The most common definitions are "truncating"
-- | division, where the result of `a / b` is rounded towards 0, and "Knuthian"
-- | or "flooring" division, where the result of `a / b` is rounded towards
-- | negative infinity. A slightly less common, but arguably more useful, option
-- | is "Euclidean" division, which is defined so as to ensure that ``a `mod` b``
-- | is always nonnegative. With Euclidean division, `a / b` rounds towards
-- | negative infinity if the divisor is positive, and towards positive infinity
-- | if the divisor is negative. Note that all three definitions are identical if
-- | we restrict our attention to nonnegative dividends and divisors.
-- |
-- | In versions 1.x, 2.x, and 3.x of the Prelude, the `EuclideanRing Int`
-- | instance used truncating division. As of 4.x, the `EuclideanRing Int`
-- | instance uses Euclidean division. Additional functions `quot` and `rem` are
-- | supplied if truncating division is desired.
class CommutativeRing a <= EuclideanRing a where
degree :: a -> Int
div :: a -> a -> a
Expand Down Expand Up @@ -77,3 +100,38 @@ lcm a b =
if a == zero || b == zero
then zero
else a * b / gcd a b

-- | The `quot` function provides _truncating_ integer division (see the
-- | documentation for the `EuclideanRing` class). It is identical to `div` in
-- | the `EuclideanRing Int` instance if the dividend is positive, but will be
-- | slightly different if the dividend is negative. For example:
-- |
-- | ```purescript
-- | div 2 3 == 0
-- | quot 2 3 == 0
-- |
-- | div (-2) 3 == (-1)
-- | quot (-2) 3 == 0
-- |
-- | div 2 (-3) == 0
-- | quot 2 (-3) == 0
-- | ```
foreign import quot :: Int -> Int -> Int

-- | The `rem` function provides the remainder after _truncating_ integer
-- | division (see the documentation for the `EuclideanRing` class). It is
-- | identical to `mod` in the `EuclideanRing Int` instance if the dividend is
-- | positive, but will be slightly different if the dividend is negative. For
-- | example:
-- |
-- | ```purescript
-- | mod 2 3 == 2
-- | rem 2 3 == 2
-- |
-- | mod (-2) 3 == 1
-- | rem (-2) 3 == (-2)
-- |
-- | mod 2 (-3) == 2
-- | rem 2 (-3) == 2
-- | ```
foreign import rem :: Int -> Int -> Int
2 changes: 1 addition & 1 deletion src/Prelude.purs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import Data.Bounded (class Bounded, bottom, top)
import Data.CommutativeRing (class CommutativeRing)
import Data.DivisionRing (class DivisionRing, recip)
import Data.Eq (class Eq, eq, notEq, (/=), (==))
import Data.EuclideanRing (class EuclideanRing, degree, div, mod, (/), gcd, lcm)
import Data.EuclideanRing (class EuclideanRing, degree, div, mod, quot, rem, (/), gcd, lcm)
import Data.Field (class Field)
import Data.Function (const, flip, ($), (#))
import Data.Functor (class Functor, flap, map, void, ($>), (<#>), (<$), (<$>), (<@>))
Expand Down
54 changes: 54 additions & 0 deletions test/Test/Main.purs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module Test.Main where

import Prelude
import Data.EuclideanRing (intDiv, intMod)
import Data.Ord (abs)

type AlmostEff = Unit -> Unit

Expand All @@ -9,6 +11,8 @@ main = do
testNumberShow show
testOrderings
testOrdUtils
testIntDivMod
testIntQuotRem
testIntDegree

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

testIntDivMod :: AlmostEff
testIntDivMod = do
-- Check when dividend goes into divisor exactly
go 8 2
go (-8) 2
go 8 (-2)
go (-8) (-2)

-- Check when dividend does not go into divisor exactly
go 2 3
go (-2) 3
go 2 (-3)
go (-2) (-3)

where
go a b =
let
q = intDiv a b
r = intMod a b
msg = show a <> " / " <> show b <> ": "
in do
assert (msg <> "Quotient/remainder law") $
q * b + r == a
assert (msg <> "Remainder should be between 0 and `abs b`, got: " <> show r) $
0 <= r && r < abs b

testIntQuotRem :: AlmostEff
testIntQuotRem = do
-- Check when dividend goes into divisor exactly
go 8 2
go (-8) 2
go 8 (-2)
go (-8) (-2)

-- Check when dividend does not go into divisor exactly
go 2 3
go (-2) 3
go 2 (-3)
go (-2) (-3)

where
go a b =
let
q = quot a b
r = rem a b
msg = show a <> " / " <> show b <> ": "
in do
assert (msg <> "Quotient/remainder law") $
q * b + r == a

testIntDegree :: AlmostEff
testIntDegree = do
let bot = bottom :: Int
Expand Down

0 comments on commit dc51981

Please sign in to comment.