diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d322f0..1805de0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ New features: Bugfixes: +- Return zero instead of `NaN` from `Data.Int.rem` when the divisor is zero (#60 by @thomashoneyman). +- Keep the FFI result of `Data.Int.Bits.zshr` within the signed 32-bit `Int` range (#60 by @thomashoneyman). Shifting a negative input by zero or a multiple of 32 now returns that input instead of converting it to an unsigned number. + Other improvements: ## [v6.0.1](https://github.com/purescript/purescript-integers/releases/tag/v6.0.1) - 2026-10-17 diff --git a/src/Data/Int.js b/src/Data/Int.js index 98bedbb..2764ca3 100644 --- a/src/Data/Int.js +++ b/src/Data/Int.js @@ -49,7 +49,7 @@ export const quot = function (x) { export const rem = function (x) { return function (y) { - return x % y; + return (x % y) | 0; }; }; diff --git a/src/Data/Int.purs b/src/Data/Int.purs index fe2a77a..13e8ac0 100644 --- a/src/Data/Int.purs +++ b/src/Data/Int.purs @@ -228,6 +228,8 @@ fromStringAs = fromStringAsImpl Just Nothing -- | div 2 (-3) == 0 -- | quot 2 (-3) == 0 -- | ``` +-- | +-- | Returns zero when the divisor is zero. foreign import quot :: Int -> Int -> Int -- | The `rem` function provides the remainder after _truncating_ integer @@ -246,6 +248,9 @@ foreign import quot :: Int -> Int -> Int -- | mod 2 (-3) == 2 -- | rem 2 (-3) == 2 -- | ``` +-- | +-- | Returns zero when the divisor is zero, consistent with `quot` and `mod`. +-- | The quotient/remainder law only applies to nonzero divisors. foreign import rem :: Int -> Int -> Int -- | Raise an Int to the power of another Int. diff --git a/src/Data/Int/Bits.js b/src/Data/Int/Bits.js index ddb6225..21299d2 100644 --- a/src/Data/Int/Bits.js +++ b/src/Data/Int/Bits.js @@ -32,7 +32,7 @@ export const shr = function (n1) { export const zshr = function (n1) { return function (n2) { - return n1 >>> n2; + return (n1 >>> n2) | 0; }; }; diff --git a/src/Data/Int/Bits.purs b/src/Data/Int/Bits.purs index 98bc5bc..307c754 100644 --- a/src/Data/Int/Bits.purs +++ b/src/Data/Int/Bits.purs @@ -34,6 +34,8 @@ foreign import shl :: Int -> Int -> Int foreign import shr :: Int -> Int -> Int -- | Bitwise zero-fill shift right. +-- | Only the low five bits of the shift count are used. The result is a signed +-- | 32-bit `Int`, so shifting by zero (or a multiple of 32) returns the input. foreign import zshr :: Int -> Int -> Int -- | Bitwise NOT. diff --git a/test/Test/Data/Int.js b/test/Test/Data/Int.js new file mode 100644 index 0000000..4073aaa --- /dev/null +++ b/test/Test/Data/Int.js @@ -0,0 +1,7 @@ +export const applyIntBinary = function (f) { + return function (x) { + return function (y) { + return f(x)(y); + }; + }; +}; diff --git a/test/Test/Data/Int.purs b/test/Test/Data/Int.purs index c01cd7d..6bc8f5c 100644 --- a/test/Test/Data/Int.purs +++ b/test/Test/Data/Int.purs @@ -3,6 +3,7 @@ module Test.Data.Int (testInt) where import Prelude import Data.Int (binary, ceil, even, floor, fromNumber, fromString, fromStringAs, hexadecimal, octal, odd, parity, pow, quot, radix, rem, round, toNumber, toStringAs) +import Data.Int.Bits (zshr) import Data.Maybe (Maybe(..), fromJust) import Data.Number (infinity, nan) import Effect (Effect) @@ -10,6 +11,10 @@ import Effect.Console (log) import Partial.Unsafe (unsafePartial) import Test.Assert (assert) +-- Keep calls opaque to the compiler's bitwise-operation inliner so these +-- assertions exercise the library's FFI implementation. +foreign import applyIntBinary :: (Int -> Int -> Int) -> Int -> Int -> Int + testInt :: Effect Unit testInt = do @@ -170,6 +175,43 @@ testInt = do go 2 (-3) go (-2) (-3) + log "quot and rem return zero for a zero divisor" + let + testZeroDivisor x = do + assert $ quot x 0 == 0 + assert $ rem x 0 == 0 + assert $ applyIntBinary rem x 0 == 0 + testZeroDivisor 0 + testZeroDivisor 5 + testZeroDivisor (-5) + testZeroDivisor bottom + testZeroDivisor top + + log "rem preserves the dividend's sign" + assert $ applyIntBinary rem (-8) 3 == (-2) + assert $ applyIntBinary rem 8 (-3) == 2 + assert $ applyIntBinary rem (-8) (-3) == (-2) + + log "zshr FFI returns signed Ints and masks shift counts to five bits" + let + testShiftCount count = do + assert $ applyIntBinary zshr (-1) count == (-1) + assert $ applyIntBinary zshr (-8) count == (-8) + assert $ applyIntBinary zshr bottom count == bottom + assert $ applyIntBinary zshr top count == top + testShiftCount 0 + testShiftCount 32 + testShiftCount 64 + testShiftCount (-32) + + log "zshr zero-fills rather than sign-extends negative inputs" + assert $ zshr (-8) 1 == 2147483644 + assert $ zshr bottom 31 == 1 + assert $ zshr (-8) 33 == 2147483644 + assert $ applyIntBinary zshr (-8) 1 == 2147483644 + assert $ applyIntBinary zshr bottom 31 == 1 + assert $ applyIntBinary zshr (-8) 33 == 2147483644 + log "pow" assert $ pow 2 2 == 4 assert $ pow 5 3 == 125