diff --git a/src/Data/EuclideanRing.js b/src/Data/EuclideanRing.js index 362b119e..e7b8ad94 100644 --- a/src/Data/EuclideanRing.js +++ b/src/Data/EuclideanRing.js @@ -4,7 +4,15 @@ 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; @@ -12,6 +20,13 @@ exports.intDiv = function (x) { }; 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; }; diff --git a/src/Data/EuclideanRing.purs b/src/Data/EuclideanRing.purs index 6d02ec1f..6a0edf2f 100644 --- a/src/Data/EuclideanRing.purs +++ b/src/Data/EuclideanRing.purs @@ -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 ((||)) @@ -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 @@ -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 diff --git a/src/Prelude.purs b/src/Prelude.purs index 3a1cd439..d6cfe2bb 100644 --- a/src/Prelude.purs +++ b/src/Prelude.purs @@ -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, ($>), (<#>), (<$), (<$>), (<@>)) diff --git a/test/Test/Main.purs b/test/Test/Main.purs index 983f5b47..177d85f3 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -1,6 +1,8 @@ module Test.Main where import Prelude +import Data.EuclideanRing (intDiv, intMod) +import Data.Ord (abs) type AlmostEff = Unit -> Unit @@ -9,6 +11,8 @@ main = do testNumberShow show testOrderings testOrdUtils + testIntDivMod + testIntQuotRem testIntDegree foreign import testNumberShow :: (Number -> String) -> AlmostEff @@ -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