summaryrefslogtreecommitdiff
path: root/libraries/base
diff options
context:
space:
mode:
authorHE, Tao <sighingnow@gmail.com>2018-05-16 12:13:16 -0400
committerBen Gamari <ben@smart-cactus.org>2018-05-16 12:58:29 -0400
commit4ffaf4b67773af4c72d92bb8b6c87b1a7d34ac0f (patch)
treec3d42ecb1e5d85363bb5070af2f54299de7551ae /libraries/base
parent99f8cc84a5b23878b3b0732955cb651bc973e9f2 (diff)
downloadhaskell-4ffaf4b67773af4c72d92bb8b6c87b1a7d34ac0f.tar.gz
Improve numeric stability of numericEnumFrom for floating numbers
Fixes #15081. Test Plan: cd libraries/base && make test TEST="enumNumeric" Reviewers: hvr, bgamari Reviewed By: bgamari Subscribers: dfeuer, simonpj, thomie, carter GHC Trac Issues: #15081 Differential Revision: https://phabricator.haskell.org/D4650
Diffstat (limited to 'libraries/base')
-rw-r--r--libraries/base/GHC/Real.hs56
-rw-r--r--libraries/base/tests/all.T1
-rw-r--r--libraries/base/tests/enumNumeric.hs7
-rw-r--r--libraries/base/tests/enumNumeric.stdout5
4 files changed, 67 insertions, 2 deletions
diff --git a/libraries/base/GHC/Real.hs b/libraries/base/GHC/Real.hs
index 938dff6bce..7f2ecd0dc5 100644
--- a/libraries/base/GHC/Real.hs
+++ b/libraries/base/GHC/Real.hs
@@ -216,10 +216,19 @@ class (Real a, Fractional a) => RealFrac a where
-- These 'numeric' enumerations come straight from the Report
numericEnumFrom :: (Fractional a) => a -> [a]
-numericEnumFrom n = n `seq` (n : numericEnumFrom (n + 1))
+numericEnumFrom n = go 0
+ where
+ -- See Note [Numeric Stability of Enumerating Floating Numbers]
+ go !k = let !n' = n + k
+ in n' : go (k + 1)
numericEnumFromThen :: (Fractional a) => a -> a -> [a]
-numericEnumFromThen n m = n `seq` m `seq` (n : numericEnumFromThen m (m+m-n))
+numericEnumFromThen n m = go 0
+ where
+ step = m - n
+ -- See Note [Numeric Stability of Enumerating Floating Numbers]
+ go !k = let !n' = n + k * step
+ in n' : go (k + 1)
numericEnumFromTo :: (Ord a, Fractional a) => a -> a -> [a]
numericEnumFromTo n m = takeWhile (<= m + 1/2) (numericEnumFrom n)
@@ -232,6 +241,49 @@ numericEnumFromThenTo e1 e2 e3
predicate | e2 >= e1 = (<= e3 + mid)
| otherwise = (>= e3 + mid)
+{- Note [Numeric Stability of Enumerating Floating Numbers]
+-----------------------------------------------------------
+When enumerate floating numbers, we could add the increment to the last number
+at every run (as what we did previously):
+
+ numericEnumFrom n = n `seq` (n : numericEnumFrom (n + 1))
+
+This approach is concise and really fast, only needs an addition operation.
+However when a floating number is large enough, for `n`, `n` and `n+1` will
+have the same binary representation. For example (all number has type
+`Double`):
+
+ 9007199254740990 is: 0x433ffffffffffffe
+ 9007199254740990 + 1 is: 0x433fffffffffffff
+ (9007199254740990 + 1) + 1 is: 0x4340000000000000
+ ((9007199254740990 + 1) + 1) + 1 is: 0x4340000000000000
+
+When we evaluate ([9007199254740990..9007199254740991] :: Double), we would
+never reach the condition in `numericEnumFromTo`
+
+ 9007199254740990 + 1 + 1 + ... > 9007199254740991 + 1/2
+
+We would fall into infinite loop (as reported in Trac #15081).
+
+To remedy the situation, we record the number of `1` that needed to be added
+to the start number, rather than increasing `1` at every time. This approach
+can improvement the numeric stability greatly at the cost of a multiplication.
+
+Furthermore, we use the type of the enumerated number, `Fractional a => a`,
+as the type of multiplier. In rare situations, the multiplier could be very
+large and will lead to the enumeration to infinite loop, too, which should
+be very rare. Consider the following example:
+
+ [1..9007199254740994]
+
+We could fix that by using an Integer as multiplier but we don't do that.
+The benchmark on T7954.hs shows that this approach leads to significant
+degeneration on performance (33% increase allocation and 300% increase on
+elapsed time).
+
+See Trac #15081 and Phab:D4650 for the related discussion about this problem.
+-}
+
--------------------------------------------------------------
-- Instances for Int
--------------------------------------------------------------
diff --git a/libraries/base/tests/all.T b/libraries/base/tests/all.T
index d530e10266..eb00fc37af 100644
--- a/libraries/base/tests/all.T
+++ b/libraries/base/tests/all.T
@@ -2,6 +2,7 @@
test('readFloat', exit_code(1), compile_and_run, [''])
test('enumDouble', normal, compile_and_run, [''])
test('enumRatio', normal, compile_and_run, [''])
+test('enumNumeric', normal, compile_and_run, [''])
test('tempfiles', normal, compile_and_run, [''])
test('fixed', normal, compile_and_run, [''])
test('quotOverflow', normal, compile_and_run, [''])
diff --git a/libraries/base/tests/enumNumeric.hs b/libraries/base/tests/enumNumeric.hs
new file mode 100644
index 0000000000..36c4846d1f
--- /dev/null
+++ b/libraries/base/tests/enumNumeric.hs
@@ -0,0 +1,7 @@
+main :: IO ()
+main = do
+ print $ map (/2) ([5..6] :: [Double])
+ print $ ([9007199254740990..9007199254740991] :: [Double])
+ print $ map (/2) ([9007199254740990..9007199254740991] :: [Double])
+ print $ ([9007199254740989..9007199254740990] :: [Double])
+ print $ map (/2) ([9007199254740989..9007199254740990] :: [Double])
diff --git a/libraries/base/tests/enumNumeric.stdout b/libraries/base/tests/enumNumeric.stdout
new file mode 100644
index 0000000000..3d7eb74f91
--- /dev/null
+++ b/libraries/base/tests/enumNumeric.stdout
@@ -0,0 +1,5 @@
+[2.5,3.0]
+[9.00719925474099e15,9.007199254740991e15,9.007199254740992e15,9.007199254740992e15]
+[4.503599627370495e15,4.5035996273704955e15,4.503599627370496e15,4.503599627370496e15]
+[9.007199254740989e15,9.00719925474099e15]
+[4.5035996273704945e15,4.503599627370495e15]