|
| 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