-
Notifications
You must be signed in to change notification settings - Fork 27
/
Fast.hs
198 lines (187 loc) · 7.94 KB
/
Fast.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
{-# LANGUAGE OverloadedStrings #-}
-- |
-- Module: Data.Text.Format.RealFloat.Fast
-- Copyright: (c) 2011 Daniel Fischer
-- Licence: BSD3
-- Maintainer: Daniel Fischer
-- Stability: experimental
-- Portability: non-portable (GHC extensions)
--
-- Fast 'Builder' representations for floating point types. The code
-- is largely taken from code in "GHC.Float" and the 'Show' instance
-- of 'Integer' in "GHC.Num" to get the sequence of digits.
module Data.Text.Format.RealFloat.Fast
( DispFloat(..)
, fshowFloat
, fshowEFloat
, fshowFFloat
, fshowGFloat
) where
import Data.Text.Format.Functions ((<>), i2d)
import Data.Text.Format.Int (integral)
import Data.Text.Format.RealFloat.Fast.Internal (posToDigits)
import Data.Text.Format.RealFloat.Functions (roundTo)
import Data.Text.Format.Types (FPFormat(..))
import Data.Text.Lazy.Builder
import qualified Data.Text as T
-- | Class for specifying display parameters. The type @a@
-- is supposed to be an IEEE-ish (real) floating-point
-- type with floating-point radix 2, such that the mantissa
-- returned by 'decodeFloat' satisfies
--
-- @
-- 2^('binExp' x) <= 'fst' ('decodeFloat' x) < 2^('binExp' x + 1)
-- @
--
-- for @x > 0@, so @'binExp' x = 'floatDigits' x - 1@.
-- The number of decimal digits that may be required is calculated
-- with the formula
--
-- @
-- 'decDigits' x = 2 + 'floor' ('floatDigits' x * 'logBase' 10 2).
-- @
--
-- The default implementation uses an approximation of
-- @'logBase' 10 2@ sufficient for mantissae of up to
-- several thousand bits. Nevertheless, hardcoding
-- the values in instance declarations may yield
-- better performance.
class (RealFloat a) => DispFloat a where
-- | The number of decimal digits that may be needed to
-- uniquely determine a value of type @a@.
-- For faster conversions which need not satisfy
--
-- @
-- x == 'read' ('fshow' x)
-- @
--
-- a smaller value can be given.
decDigits :: a -> Int
decDigits x = 2 + (8651*(floatDigits x)) `quot` 28738
-- | The base 2 logarithm of the mantissa returned by
-- @'decodeFloat' x@ for @x > 0@.
binExp :: a -> Int
binExp x = floatDigits x - 1
instance DispFloat Double where
decDigits _ = 17
binExp _ = 52
instance DispFloat Float where
decDigits _ = 9
binExp _ = 23
-- | Show a signed 'DispFloat' value to full precision
-- using standard decimal notation for arguments whose absolute value lies
-- between @0.1@ and @9,999,999@, and scientific notation otherwise.
-- Analogous to @'showFloat'@ from "GHC.Float".
fshowFloat :: (DispFloat a) => a -> Builder
{-# SPECIALIZE fshowFloat :: Float -> Builder #-}
{-# SPECIALIZE fshowFloat :: Double -> Builder #-}
fshowFloat x = formatFloat Generic Nothing x
-- | Show a signed 'DispFloat' value
-- using scientific (exponential) notation (e.g. @2.45e2@, @1.5e-3@).
--
-- In the call @'fshowEFloat' digs val@, if @digs@ is 'Nothing',
-- the value is shown to full precision; if @digs@ is @'Just' d@,
-- then @'max' 1 d@ digits after the decimal point are shown.
-- Analogous to @'showEFloat'@ from "Numeric".
fshowEFloat :: (DispFloat a) => Maybe Int -> a -> Builder
{-# SPECIALIZE fshowEFloat :: Maybe Int -> Float -> Builder #-}
{-# SPECIALIZE fshowEFloat :: Maybe Int -> Double -> Builder #-}
fshowEFloat d x = formatFloat Exponent d x
-- | Show a signed 'DispFloat' value
-- using standard decimal notation (e.g. @245000@, @0.0015@).
--
-- In the call @'fshowFFloat' digs val@, if @digs@ is 'Nothing',
-- the value is shown to full precision; if @digs@ is @'Just' d@,
-- then @'max' 0 d@ digits after the decimal point are shown.
-- Analogous to @'showFFloat'@ from "Numeric".
fshowFFloat :: (DispFloat a) => Maybe Int -> a -> Builder
{-# SPECIALIZE fshowFFloat :: Maybe Int -> Float -> Builder #-}
{-# SPECIALIZE fshowFFloat :: Maybe Int -> Double -> Builder #-}
fshowFFloat d x = formatFloat Fixed d x
-- | Show a signed 'DispFloat' value
-- using standard decimal notation for arguments whose absolute value lies
-- between @0.1@ and @9,999,999@, and scientific notation otherwise.
--
-- In the call @'fshowGFloat' digs val@, if @digs@ is 'Nothing',
-- the value is shown to full precision; if @digs@ is @'Just' d@,
-- then @'max' 1 d@ digits after the decimal point are shown.
-- Analogous to @'showGFloat'@ from "Numeric".
fshowGFloat :: (DispFloat a) => Maybe Int -> a -> Builder
{-# SPECIALIZE fshowGFloat :: Maybe Int -> Float -> Builder #-}
{-# SPECIALIZE fshowGFloat :: Maybe Int -> Double -> Builder #-}
fshowGFloat d x = formatFloat Generic d x
formatFloat :: DispFloat a => FPFormat -> Maybe Int -> a -> Builder
{-# SPECIALIZE formatFloat :: FPFormat -> Maybe Int -> Float -> Builder #-}
{-# SPECIALIZE formatFloat :: FPFormat -> Maybe Int -> Double -> Builder #-}
formatFloat fmt decs x
| isNaN x = "NaN"
| isInfinite x = if x < 0 then "-Infinity" else "Infinity"
| x < 0 || isNegativeZero x = singleton '-' <> doFmt fmt (fltDigs (-x))
| otherwise = doFmt fmt (fltDigs x)
where
fltDigs 0 = ([0],0)
fltDigs y = uncurry (posToDigits (decDigits y) (binExp y)) (decodeFloat y)
fluff :: [Int] -> [Int]
fluff [] = [0]
fluff xs = xs
doFmt format (is, e) =
case format of
Generic ->
doFmt (if e < 0 || e > 7 then Exponent else Fixed) (is,e)
Exponent ->
case decs of
Nothing ->
let show_e' = integral $ if ei == 0 then (e-1) else e
(ei,(d:ds)) = roundToS (decDigits x) is
in case is of
[0] -> "0.0e0"
_ -> singleton (i2d d) <> singleton '.' <> fromString (map i2d (fluff ds)) <> singleton 'e' <> show_e'
Just dec ->
let dec' = max dec 1 in
case is of
[0] -> fromText "0." <> fromText (T.replicate dec' "0") <> "e0"
_ -> let (ei,is') = roundTo (dec'+1) is
(d:ds') = map i2d (if ei > 0 then init is' else is')
in singleton d <> singleton '.' <> fromString ds' <> singleton 'e' <> integral (e-1+ei)
Fixed ->
let mk0 ls = case ls of { "" -> "0" ; _ -> fromString ls} in
case decs of
Nothing ->
let (ei, is') = roundToS (decDigits x) is
e' = e+ei
ds = map i2d is'
in case is of
[0] -> "0.0"
_ | e' <= 0 -> "0." <> fromText (T.replicate (-e') "0") <> fromString (map i2d is')
| otherwise ->
let f 0 s rs = mk0 (reverse s) <> singleton '.' <> mk0 rs
f n s "" = f (n-1) ('0':s) ""
f n s (r:rs) = f (n-1) (r:s) rs
in f e' "" ds
Just dec ->
let dec' = max dec 0 in
if e >= 0 then
let (ei,is') = roundTo (dec' + e) is
(ls,rs) = splitAt (e+ei) (map i2d is')
in mk0 ls <> (if null rs then "" else singleton '.' <> fromString rs)
else
let (ei,is') = roundTo dec' (replicate (-e) 0 ++ is)
d:ds' = map i2d (if ei > 0 then is' else 0:is')
in singleton d <> (if null ds' then "" else singleton '.' <> fromString ds')
roundToS :: Int -> [Int] -> (Int,[Int])
roundToS d is =
case f d is of
x@(0,_) -> x
(1,xs) -> (1, 1:xs)
_ -> error "roundToS: bad Value"
where
f _ [] = (0, [])
f 0 (x:_) = (if x >= 5 then 1 else 0, [])
f n (i:xs)
| i' == 10 = (1,prep 0 ds)
| otherwise = (0,prep i' ds)
where
prep 0 [] = []
prep a bs = a:bs
(c,ds) = f (n-1) xs
i' = c + i