Skip to content

Switch to Euclidean division for Int, resolves #161 #168

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should provide quot and rem in purescript-integers, since they are Int specialised, rather than constrained to EuclideanRing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that wouldn't be a bad option. I think it makes sense to put them here, because they do form a valid EuclideanRing instance (even if we're no longer using it as the default instance for Int), and also because these functions were available from Prelude before, but I don't have a strong preference.

2 changes: 1 addition & 1 deletion src/Prelude.purs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,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