{-# LANGUAGE CPP, OverloadedStrings #-} module Data.ByteString.Builder.Scientific ( scientificBuilder , formatScientificBuilder , FPFormat(..) ) where import Data.Scientific (Scientific) import qualified Data.Scientific as Scientific import Data.Text.Lazy.Builder.RealFloat (FPFormat(..)) import qualified Data.ByteString.Char8 as BC8 import Data.ByteString.Builder (Builder, string8, char8, intDec) import Data.ByteString.Builder.Extra (byteStringCopy) import Utils (roundTo, i2d) #if !MIN_VERSION_base(4,8,0) import Data.Monoid (mempty) #endif #if MIN_VERSION_base(4,5,0) import Data.Monoid ((<>)) #else import Data.Monoid (Monoid, mappend) (<>) :: Monoid a => a -> a -> a (<>) = mappend infixr 6 <> #endif -- | A @ByteString@ @Builder@ which renders a scientific number to full -- precision, using standard decimal notation for arguments whose -- absolute value lies between @0.1@ and @9,999,999@, and scientific -- notation otherwise. scientificBuilder :: Scientific -> Builder scientificBuilder = formatScientificBuilder Generic Nothing -- | Like 'scientificBuilder' but provides rendering options. formatScientificBuilder :: FPFormat -> Maybe Int -- ^ Number of decimal places to render. -> Scientific -> Builder formatScientificBuilder fmt decs scntfc | scntfc < 0 = char8 '-' <> doFmt fmt (Scientific.toDecimalDigits (-scntfc)) | otherwise = doFmt fmt (Scientific.toDecimalDigits scntfc) where doFmt format (is, e) = let ds = map i2d is in case format of Generic -> doFmt (if e < 0 || e > 7 then Exponent else Fixed) (is,e) Exponent -> case decs of Nothing -> let show_e' = intDec (e-1) in case ds of "0" -> byteStringCopy "0.0e0" [d] -> char8 d <> byteStringCopy ".0e" <> show_e' (d:ds') -> char8 d <> char8 '.' <> string8 ds' <> char8 'e' <> show_e' [] -> error $ "Data.ByteString.Builder.Scientific.formatScientificBuilder" ++ "/doFmt/Exponent: []" Just dec -> let dec' = max dec 1 in case is of [0] -> byteStringCopy "0." <> byteStringCopy (BC8.replicate dec' '0') <> byteStringCopy "e0" _ -> let (ei,is') = roundTo (dec'+1) is (d:ds') = map i2d (if ei > 0 then init is' else is') in char8 d <> char8 '.' <> string8 ds' <> char8 'e' <> intDec (e-1+ei) Fixed -> let mk0 ls = case ls of { "" -> char8 '0' ; _ -> string8 ls} in case decs of Nothing | e <= 0 -> byteStringCopy "0." <> byteStringCopy (BC8.replicate (-e) '0') <> string8 ds | otherwise -> let f 0 s rs = mk0 (reverse s) <> char8 '.' <> 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 mempty else char8 '.' <> string8 rs) else let (ei,is') = roundTo dec' (replicate (-e) 0 ++ is) d:ds' = map i2d (if ei > 0 then is' else 0:is') in char8 d <> (if null ds' then mempty else char8 '.' <> string8 ds')