Skip to content

Commit f65dde3

Browse files
committed
Implement floating point conversion with ryu
1 parent d52d42d commit f65dde3

File tree

10 files changed

+1589
-20
lines changed

10 files changed

+1589
-20
lines changed

Data/ByteString/Builder.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,15 @@ module Data.ByteString.Builder
252252
, stringUtf8
253253

254254
, module Data.ByteString.Builder.ASCII
255+
, module Data.ByteString.Builder.RealFloat
255256

256257
) where
257258

258259
import Data.ByteString.Builder.Internal
259260
import qualified Data.ByteString.Builder.Prim as P
260261
import qualified Data.ByteString.Lazy.Internal as L
261262
import Data.ByteString.Builder.ASCII
263+
import Data.ByteString.Builder.RealFloat
262264

263265
import Data.String (IsString(..))
264266
import System.IO (Handle)

Data/ByteString/Builder/ASCII.hs

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ module Data.ByteString.Builder.ASCII
3636
, word64Dec
3737
, wordDec
3838

39-
, floatDec
40-
, doubleDec
41-
4239
-- *** Hexadecimal numbers
4340

4441
-- | Encoding positive integers as hexadecimal numbers using lower-case
@@ -191,22 +188,6 @@ wordDec :: Word -> Builder
191188
wordDec = P.primBounded P.wordDec
192189

193190

194-
-- Floating point numbers
195-
-------------------------
196-
197-
-- TODO: Use Bryan O'Sullivan's double-conversion package to speed it up.
198-
199-
-- | /Currently slow./ Decimal encoding of an IEEE 'Float'.
200-
{-# INLINE floatDec #-}
201-
floatDec :: Float -> Builder
202-
floatDec = string7 . show
203-
204-
-- | /Currently slow./ Decimal encoding of an IEEE 'Double'.
205-
{-# INLINE doubleDec #-}
206-
doubleDec :: Double -> Builder
207-
doubleDec = string7 . show
208-
209-
210191
------------------------------------------------------------------------------
211192
-- Hexadecimal Encoding
212193
------------------------------------------------------------------------------

Data/ByteString/Builder/RealFloat.hs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
2+
module Data.ByteString.Builder.RealFloat
3+
( FFFormat(..)
4+
, floatDec
5+
, doubleDec
6+
, formatFloat
7+
, formatDouble
8+
) where
9+
10+
import Data.ByteString.Builder.Internal (Builder)
11+
import qualified Data.ByteString.Builder.RealFloat.Internal as R
12+
import qualified Data.ByteString.Builder.RealFloat.F2S as RF
13+
import qualified Data.ByteString.Builder.RealFloat.D2S as RD
14+
import qualified Data.ByteString.Builder.Prim as BP
15+
import GHC.Float (FFFormat(..), roundTo)
16+
import GHC.Word (Word64(..))
17+
import GHC.Show (intToDigit)
18+
19+
{-# INLINABLE floatDec #-}
20+
floatDec :: Float -> Builder
21+
floatDec = formatFloat FFGeneric Nothing
22+
23+
{-# INLINABLE doubleDec #-}
24+
doubleDec :: Double -> Builder
25+
doubleDec = formatDouble FFGeneric Nothing
26+
27+
{-# INLINABLE formatFloat #-}
28+
-- TODO precision for general and exponent formats
29+
formatFloat :: FFFormat -> Maybe Int -> Float -> Builder
30+
formatFloat fmt prec f =
31+
case fmt of
32+
FFGeneric ->
33+
case specialStr f of
34+
Just b -> b
35+
Nothing ->
36+
if e' >= 0 && e' <= 7
37+
then sign f `mappend` showFixed (fromIntegral m) e' prec
38+
else BP.primBounded (R.toCharsScientific (f < 0) m e) ()
39+
where (RF.FloatingDecimal m e) = RF.f2Intermediate f
40+
e' = fromIntegral e + R.decimalLength9 m
41+
FFExponent -> RF.f2s f
42+
FFFixed -> d2Fixed (realToFrac f) prec
43+
44+
{-# INLINABLE formatDouble #-}
45+
formatDouble :: FFFormat -> Maybe Int -> Double -> Builder
46+
-- TODO precision for general and exponent formats
47+
formatDouble fmt prec f =
48+
case fmt of
49+
FFGeneric ->
50+
case specialStr f of
51+
Just b -> b
52+
Nothing ->
53+
if e' >= 0 && e' <= 7
54+
then sign f `mappend` showFixed m e' prec
55+
else BP.primBounded (R.toCharsScientific (f < 0) m e) ()
56+
where (RD.FloatingDecimal m e) = RD.d2Intermediate f
57+
e' = fromIntegral e + R.decimalLength17 m
58+
FFExponent -> RD.d2s f
59+
FFFixed -> d2Fixed f prec
60+
61+
-- show fixed floating point matching show / showFFloat output by dropping
62+
-- digits after exponentiation precision
63+
d2Fixed :: Double -> Maybe Int -> Builder
64+
d2Fixed f prec =
65+
case specialStr f of
66+
Just b -> b
67+
Nothing -> sign f `mappend` showFixed m e' prec
68+
where (RD.FloatingDecimal m e) = RD.d2Intermediate f
69+
olength = R.decimalLength17 m
70+
-- NB: exponent in exponential format is e' - 1
71+
e' = fromIntegral e + olength
72+
73+
-- | Char7 encode a 'Char'.
74+
{-# INLINE char7 #-}
75+
char7 :: Char -> Builder
76+
char7 = BP.primFixed BP.char7
77+
78+
-- | Char7 encode a 'String'.
79+
{-# INLINE string7 #-}
80+
string7 :: String -> Builder
81+
string7 = BP.primMapListFixed BP.char7
82+
83+
sign :: RealFloat a => a -> Builder
84+
sign f = if f < 0 then char7 '-' else mempty
85+
86+
specialStr :: RealFloat a => a -> Maybe Builder
87+
specialStr f
88+
| isNaN f = Just $ string7 "NaN"
89+
| isInfinite f = Just $ sign f `mappend` string7 "Infinity"
90+
| isNegativeZero f = Just $ string7 "-0.0"
91+
| f == 0 = Just $ string7 "0.0"
92+
| otherwise = Nothing
93+
94+
digits :: Word64 -> [Int]
95+
digits w = go [] w
96+
where go ds 0 = ds
97+
go ds c = let (q, r) = R.dquotRem10Boxed c
98+
in go (fromIntegral r:ds) q
99+
100+
showFixed :: Word64 -> Int -> Maybe Int -> Builder
101+
showFixed m e prec =
102+
case prec of
103+
Nothing
104+
| e <= 0 -> char7 '0'
105+
`mappend` char7 '.'
106+
`mappend` string7 (replicate (-e) '0')
107+
`mappend` mconcat (digitsToBuilder ds)
108+
| otherwise ->
109+
let f 0 s rs = mk0 (reverse s) `mappend` char7 '.' `mappend` mk0 rs
110+
f n s [] = f (n-1) (char7 '0':s) []
111+
f n s (r:rs) = f (n-1) (r:s) rs
112+
in f e [] (digitsToBuilder ds)
113+
Just p
114+
| e >= 0 ->
115+
let (ei, is') = roundTo 10 (p' + e) ds
116+
(ls, rs) = splitAt (e + ei) (digitsToBuilder is')
117+
in mk0 ls `mappend` mkDot rs
118+
| otherwise ->
119+
let (ei, is') = roundTo 10 p' (replicate (-e) 0 ++ ds)
120+
(b:bs) = digitsToBuilder (if ei > 0 then is' else 0:is')
121+
in b `mappend` mkDot bs
122+
where p' = max p 0
123+
where
124+
mk0 ls = case ls of [] -> char7 '0'; _ -> mconcat ls
125+
mkDot rs = if null rs then mempty else char7 '.' `mappend` mconcat rs
126+
ds = digits m
127+
digitsToBuilder = fmap (char7 . intToDigit)
128+

0 commit comments

Comments
 (0)