diff --git a/src/Data/Int.erl b/src/Data/Int.erl index 82ebe4d..b85f584 100644 --- a/src/Data/Int.erl +++ b/src/Data/Int.erl @@ -1,10 +1,12 @@ -module(data_int@foreign). -export([fromNumberImpl/3,toNumber/1,fromStringAsImpl/4,toStringAs/2,pow/2,quot/2,'rem'/2]). -fromNumberImpl(Just, Nothing, N) -> - case trunc(N) of - N1 when N1 == N -> Just(N1); - _ -> Nothing +fromNumberImpl(Just, Nothing, N) when is_float(N) -> + %% this uses only float ops that should be trivial and exact + Abs = abs(N), + AbsFloor = math:floor(Abs), + if AbsFloor =:= Abs, Abs =< 9.007199254740991e15 -> Just(trunc(N)); + true -> Nothing end. toNumber(N) -> float(N). @@ -18,4 +20,4 @@ pow(X,Y) -> trunc(math:pow(X,Y)). quot(X, Y) -> X div Y. -'rem'(X, Y) -> X rem Y. \ No newline at end of file +'rem'(X, Y) -> X rem Y. diff --git a/src/Data/Int.purs b/src/Data/Int.purs index e18ffd8..f824ad1 100644 --- a/src/Data/Int.purs +++ b/src/Data/Int.purs @@ -60,10 +60,14 @@ ceil = unsafeClamp <<< Math.ceil round :: Number -> Int round = unsafeClamp <<< Math.round --- | Convert an integral `Number` to an `Int`, by clamping to the `Int` range. --- | This function will return 0 if the input is `NaN` or an `Infinity`. +-- | Convert an integral `Number` to an `Int`, by clamping to the range of +-- | integers which have an exact representation as floats. This function +-- | will return 0 if the input is `NaN` or an `Infinity`. unsafeClamp :: Number -> Int -unsafeClamp x = fromMaybe 0 (fromNumber x) +unsafeClamp x + | x >= 9007199254740991.0 = 9007199254740991 + | x <= -9007199254740991.0 = -9007199254740991 + | otherwise = fromMaybe 0 (fromNumber x) -- | Converts an `Int` value back into a `Number`. Any `Int` is a valid `Number` -- | so there is no loss of precision with this function. diff --git a/test/Test/Data/Int.purs b/test/Test/Data/Int.purs index f559f79..469f4c3 100644 --- a/test/Test/Data/Int.purs +++ b/test/Test/Data/Int.purs @@ -38,18 +38,19 @@ testInt = do assert $ floor 0.3 == 0 assert $ floor 0.7 == 0 - -- log "round, ceil, and floor should clamp values outside the Int range" - -- let testClamping f = do - -- let low = toNumber bottom - 1.5 - -- assert $ f low == bottom - -- - -- let high = toNumber top + 1.5 - -- assert $ f high == top - - -- testClamping round - -- testClamping ceil - -- testClamping floor - + log "round, ceil, and floor should clamp values outside the exact range" + let testClamping f = do + let bottom = -9007199254740991 + let low = toNumber bottom - 1.5 + assert $ f low == bottom + + let top = 9007199254740991 + let high = toNumber top + 1.5 + assert $ f high == top + + testClamping round + testClamping ceil + testClamping floor -- log "round, ceil, and floor should return 0 for NaN and Infinities" -- let testNonNumber f = do