-- |-- Module : Data.ByteString.Builder.RealFloat-- Copyright : (c) Lawrence Wu 2021-- License : BSD-style-- Maintainer : lawrencejwu@gmail.com---- Floating point formatting for @Bytestring.Builder@---- This module primarily exposes `floatDec` and `doubleDec` which do the-- equivalent of converting through @'Data.ByteString.Builder.string7' . 'show'@.---- It also exposes `formatFloat` and `formatDouble` with a similar API as-- `GHC.Float.formatRealFloat`.---- NB: The float-to-string conversions exposed by this module match `show`'s-- output (specifically with respect to default rounding and length). In-- particular, there are boundary cases where the closest and \'shortest\'-- string representations are not used. Mentions of \'shortest\' in the docs-- below are with this caveat.---- For example, for fidelity, we match `show` on the output below.---- >>> show (1.0e23 :: Float)-- "1.0e23"-- >>> show (1.0e23 :: Double)-- "9.999999999999999e22"-- >>> floatDec 1.0e23-- "1.0e23"-- >>> doubleDec 1.0e23-- "9.999999999999999e22"---- Simplifying, we can build a shorter, lossless representation by just using-- @"1.0e23"@ since the floating point values that are 1 ULP away are---- >>> showHex (castDoubleToWord64 1.0e23) []-- "44b52d02c7e14af6"-- >>> castWord64ToDouble 0x44b52d02c7e14af5-- 9.999999999999997e22-- >>> castWord64ToDouble 0x44b52d02c7e14af6-- 9.999999999999999e22-- >>> castWord64ToDouble 0x44b52d02c7e14af7-- 1.0000000000000001e23---- In particular, we could use the exact boundary if it is the shortest-- representation and the original floating number is even. To experiment with-- the shorter rounding, refer to-- `Data.ByteString.Builder.RealFloat.Internal.acceptBounds`. This will give us---- >>> floatDec 1.0e23-- "1.0e23"-- >>> doubleDec 1.0e23-- "1.0e23"---- For more details, please refer to the-- <https://dl.acm.org/doi/10.1145/3192366.3192369 Ryu paper>.---- @since 0.11.2.0moduleData.ByteString.Builder.RealFloat(floatDec ,doubleDec -- * Custom formatting,formatFloat ,formatDouble ,FloatFormat ,standard ,standardDefaultPrecision ,scientific ,generic )whereimportData.ByteString.Builder.Internal (Builder )importqualifiedData.ByteString.Builder.RealFloat.Internal asRimportqualifiedData.ByteString.Builder.RealFloat.F2S asRFimportqualifiedData.ByteString.Builder.RealFloat.D2S asRDimportqualifiedData.ByteString.Builder.Prim asBPimportGHC.Float(roundTo)importGHC.Word(Word64)importGHC.Show(intToDigit)-- | Returns a rendered Float. Matches `show` in displaying in standard or-- scientific notation---- @-- floatDec = 'formatFloat' 'generic'-- @{-# INLINABLEfloatDec #-}floatDec ::Float->Builder floatDec :: Float -> Builder floatDec =FloatFormat -> Float -> Builder formatFloat FloatFormat generic -- | Returns a rendered Double. Matches `show` in displaying in standard or-- scientific notation---- @-- doubleDec = 'formatDouble' 'generic'-- @{-# INLINABLEdoubleDec #-}doubleDec ::Double->Builder doubleDec :: Double -> Builder doubleDec =FloatFormat -> Double -> Builder formatDouble FloatFormat generic -- | Format type for use with `formatFloat` and `formatDouble`.---- @since 0.11.2.0dataFloatFormat =MkFloatFormat FormatMode (MaybeInt)-- | Standard notation with `n` decimal places---- @since 0.11.2.0standard ::Int->FloatFormat standard :: Int -> FloatFormat standard Int n =FormatMode -> Maybe Int -> FloatFormat MkFloatFormat FormatMode FStandard (Int -> Maybe Int forall a. a -> Maybe a JustInt n )-- | Standard notation with the \'default precision\' (decimal places matching `show`)---- @since 0.11.2.0standardDefaultPrecision ::FloatFormat standardDefaultPrecision :: FloatFormat standardDefaultPrecision =FormatMode -> Maybe Int -> FloatFormat MkFloatFormat FormatMode FStandard Maybe Int forall a. Maybe a Nothing-- | Scientific notation with \'default precision\' (decimal places matching `show`)---- @since 0.11.2.0scientific ::FloatFormat scientific :: FloatFormat scientific =FormatMode -> Maybe Int -> FloatFormat MkFloatFormat FormatMode FScientific Maybe Int forall a. Maybe a Nothing-- | Standard or scientific notation depending on the exponent. Matches `show`---- @since 0.11.2.0generic ::FloatFormat generic :: FloatFormat generic =FormatMode -> Maybe Int -> FloatFormat MkFloatFormat FormatMode FGeneric Maybe Int forall a. Maybe a Nothing-- | ByteString float-to-string formatdataFormatMode =FScientific -- ^ scientific notation|FStandard -- ^ standard notation with `Maybe Int` digits after the decimal|FGeneric -- ^ dispatches to scientific or standard notation based on the exponentderivingInt -> FormatMode -> ShowS [FormatMode] -> ShowS FormatMode -> String (Int -> FormatMode -> ShowS) -> (FormatMode -> String) -> ([FormatMode] -> ShowS) -> Show FormatMode forall a. (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a $cshowsPrec :: Int -> FormatMode -> ShowS showsPrec :: Int -> FormatMode -> ShowS $cshow :: FormatMode -> String show :: FormatMode -> String $cshowList :: [FormatMode] -> ShowS showList :: [FormatMode] -> ShowS Show-- TODO: support precision argument for FGeneric and FScientific-- | Returns a rendered Float. Returns the \'shortest\' representation in-- scientific notation and takes an optional precision argument in standard-- notation. Also see `floatDec`.---- With standard notation, the precision argument is used to truncate (or-- extend with 0s) the \'shortest\' rendered Float. The \'default precision\' does-- no such modifications and will return as many decimal places as the-- representation demands.---- e.g---- >>> formatFloat (standard 1) 1.2345e-2-- "0.0"-- >>> formatFloat (standard 10) 1.2345e-2-- "0.0123450000"-- >>> formatFloat standardDefaultPrecision 1.2345e-2-- "0.01234"-- >>> formatFloat scientific 12.345-- "1.2345e1"-- >>> formatFloat generic 12.345-- "12.345"---- @since 0.11.2.0{-# INLINABLEformatFloat #-}formatFloat ::FloatFormat ->Float->Builder formatFloat :: FloatFormat -> Float -> Builder formatFloat (MkFloatFormat FormatMode fmt Maybe Int prec )=\Float f ->let(RF.FloatingDecimal Word32 m Int32 e )=Float -> FloatingDecimal RF.f2Intermediate Float f e' :: Int e' =Int32 -> Int R.int32ToInt Int32 e Int -> Int -> Int forall a. Num a => a -> a -> a +Word32 -> Int R.decimalLength9 Word32 m incaseFormatMode fmt ofFormatMode FGeneric ->caseFloat -> Maybe Builder forall a. RealFloat a => a -> Maybe Builder specialStr Float f ofJustBuilder b ->Builder b Maybe Builder Nothing->ifInt e' Int -> Int -> Bool forall a. Ord a => a -> a -> Bool >=Int 0Bool -> Bool -> Bool &&Int e' Int -> Int -> Bool forall a. Ord a => a -> a -> Bool <=Int 7thenFloat -> Builder forall a. RealFloat a => a -> Builder sign Float f Builder -> Builder -> Builder forall a. Monoid a => a -> a -> a `mappend`Word64 -> Int -> Maybe Int -> Builder showStandard (Word32 -> Word64 R.word32ToWord64 Word32 m )Int e' Maybe Int prec elseBoundedPrim () -> () -> Builder forall a. BoundedPrim a -> a -> Builder BP.primBounded (Bool -> Word32 -> Int32 -> BoundedPrim () forall a. Mantissa a => Bool -> a -> Int32 -> BoundedPrim () R.toCharsScientific (Float f Float -> Float -> Bool forall a. Ord a => a -> a -> Bool <Float 0)Word32 m Int32 e )()FormatMode FScientific ->Float -> Builder RF.f2s Float f FormatMode FStandard ->caseFloat -> Maybe Builder forall a. RealFloat a => a -> Maybe Builder specialStr Float f ofJustBuilder b ->Builder b Maybe Builder Nothing->Float -> Builder forall a. RealFloat a => a -> Builder sign Float f Builder -> Builder -> Builder forall a. Monoid a => a -> a -> a `mappend`Word64 -> Int -> Maybe Int -> Builder showStandard (Word32 -> Word64 R.word32ToWord64 Word32 m )Int e' Maybe Int prec -- TODO: support precision argument for FGeneric and FScientific-- | Returns a rendered Double. Returns the \'shortest\' representation in-- scientific notation and takes an optional precision argument in standard-- notation. Also see `doubleDec`.---- With standard notation, the precision argument is used to truncate (or-- extend with 0s) the \'shortest\' rendered Float. The \'default precision\'-- does no such modifications and will return as many decimal places as the-- representation demands.---- e.g---- >>> formatDouble (standard 1) 1.2345e-2-- "0.0"-- >>> formatDouble (standard 10) 1.2345e-2-- "0.0123450000"-- >>> formatDouble standardDefaultPrecision 1.2345e-2-- "0.01234"-- >>> formatDouble scientific 12.345-- "1.2345e1"-- >>> formatDouble generic 12.345-- "12.345"---- @since 0.11.2.0{-# INLINABLEformatDouble #-}formatDouble ::FloatFormat ->Double->Builder formatDouble :: FloatFormat -> Double -> Builder formatDouble (MkFloatFormat FormatMode fmt Maybe Int prec )=\Double f ->let(RD.FloatingDecimal Word64 m Int32 e )=Double -> FloatingDecimal RD.d2Intermediate Double f e' :: Int e' =Int32 -> Int R.int32ToInt Int32 e Int -> Int -> Int forall a. Num a => a -> a -> a +Word64 -> Int R.decimalLength17 Word64 m incaseFormatMode fmt ofFormatMode FGeneric ->caseDouble -> Maybe Builder forall a. RealFloat a => a -> Maybe Builder specialStr Double f ofJustBuilder b ->Builder b Maybe Builder Nothing->ifInt e' Int -> Int -> Bool forall a. Ord a => a -> a -> Bool >=Int 0Bool -> Bool -> Bool &&Int e' Int -> Int -> Bool forall a. Ord a => a -> a -> Bool <=Int 7thenDouble -> Builder forall a. RealFloat a => a -> Builder sign Double f Builder -> Builder -> Builder forall a. Monoid a => a -> a -> a `mappend`Word64 -> Int -> Maybe Int -> Builder showStandard Word64 m Int e' Maybe Int prec elseBoundedPrim () -> () -> Builder forall a. BoundedPrim a -> a -> Builder BP.primBounded (Bool -> Word64 -> Int32 -> BoundedPrim () forall a. Mantissa a => Bool -> a -> Int32 -> BoundedPrim () R.toCharsScientific (Double f Double -> Double -> Bool forall a. Ord a => a -> a -> Bool <Double 0)Word64 m Int32 e )()FormatMode FScientific ->Double -> Builder RD.d2s Double f FormatMode FStandard ->caseDouble -> Maybe Builder forall a. RealFloat a => a -> Maybe Builder specialStr Double f ofJustBuilder b ->Builder b Maybe Builder Nothing->Double -> Builder forall a. RealFloat a => a -> Builder sign Double f Builder -> Builder -> Builder forall a. Monoid a => a -> a -> a `mappend`Word64 -> Int -> Maybe Int -> Builder showStandard Word64 m Int e' Maybe Int prec -- | Char7 encode a 'Char'.{-# INLINEchar7 #-}char7 ::Char->Builder char7 :: Char -> Builder char7 =FixedPrim Char -> Char -> Builder forall a. FixedPrim a -> a -> Builder BP.primFixed FixedPrim Char BP.char7 -- | Char7 encode a 'String'.{-# INLINEstring7 #-}string7 ::String->Builder string7 :: String -> Builder string7 =FixedPrim Char -> String -> Builder forall a. FixedPrim a -> [a] -> Builder BP.primMapListFixed FixedPrim Char BP.char7 -- | Encodes a `-` if input is negativesign ::RealFloata =>a ->Builder sign :: forall a. RealFloat a => a -> Builder sign a f =ifa f a -> a -> Bool forall a. Ord a => a -> a -> Bool <a 0thenChar -> Builder char7 Char '-'elseBuilder forall a. Monoid a => a mempty-- | Special rendering for Nan, Infinity, and 0. See-- RealFloat.Internal.NonNumbersAndZerospecialStr ::RealFloata =>a ->MaybeBuilder specialStr :: forall a. RealFloat a => a -> Maybe Builder specialStr a f |a -> Bool forall a. RealFloat a => a -> Bool isNaNa f =Builder -> Maybe Builder forall a. a -> Maybe a Just(Builder -> Maybe Builder) -> Builder -> Maybe Builder forall a b. (a -> b) -> a -> b $String -> Builder string7 String "NaN"|a -> Bool forall a. RealFloat a => a -> Bool isInfinitea f =Builder -> Maybe Builder forall a. a -> Maybe a Just(Builder -> Maybe Builder) -> Builder -> Maybe Builder forall a b. (a -> b) -> a -> b $a -> Builder forall a. RealFloat a => a -> Builder sign a f Builder -> Builder -> Builder forall a. Monoid a => a -> a -> a `mappend`String -> Builder string7 String "Infinity"|a -> Bool forall a. RealFloat a => a -> Bool isNegativeZeroa f =Builder -> Maybe Builder forall a. a -> Maybe a Just(Builder -> Maybe Builder) -> Builder -> Maybe Builder forall a b. (a -> b) -> a -> b $String -> Builder string7 String "-0.0"|a f a -> a -> Bool forall a. Eq a => a -> a -> Bool ==a 0=Builder -> Maybe Builder forall a. a -> Maybe a Just(Builder -> Maybe Builder) -> Builder -> Maybe Builder forall a b. (a -> b) -> a -> b $String -> Builder string7 String "0.0"|Bool otherwise=Maybe Builder forall a. Maybe a Nothing-- | Returns a list of decimal digits in a Word64digits ::Word64->[Int]digits :: Word64 -> [Int] digits Word64 w =[Int] -> Word64 -> [Int] go []Word64 w wherego :: [Int] -> Word64 -> [Int] go [Int] ds Word64 0=[Int] ds go [Int] ds Word64 c =let(Word64 q ,Word64 r )=Word64 -> (Word64, Word64) R.dquotRem10 Word64 c in[Int] -> Word64 -> [Int] go ((Word64 -> Int R.word64ToInt Word64 r )Int -> [Int] -> [Int] forall a. a -> [a] -> [a] :[Int] ds )Word64 q -- | Show a floating point value in standard notation. Based on GHC.Float.showFloatshowStandard ::Word64->Int->MaybeInt->Builder showStandard :: Word64 -> Int -> Maybe Int -> Builder showStandard Word64 m Int e Maybe Int prec =caseMaybe Int prec ofMaybe Int Nothing|Int e Int -> Int -> Bool forall a. Ord a => a -> a -> Bool <=Int 0->Char -> Builder char7 Char '0'Builder -> Builder -> Builder forall a. Monoid a => a -> a -> a `mappend`Char -> Builder char7 Char '.'Builder -> Builder -> Builder forall a. Monoid a => a -> a -> a `mappend`String -> Builder string7 (Int -> Char -> String forall a. Int -> a -> [a] replicate(-Int e )Char '0')Builder -> Builder -> Builder forall a. Monoid a => a -> a -> a `mappend`[Builder] -> Builder forall a. Monoid a => [a] -> a mconcat([Int] -> [Builder] digitsToBuilder [Int] ds )|Bool otherwise->letf :: t -> [Builder] -> [Builder] -> Builder f t 0[Builder] s [Builder] rs =[Builder] -> Builder mk0 ([Builder] -> [Builder] forall a. [a] -> [a] reverse[Builder] s )Builder -> Builder -> Builder forall a. Monoid a => a -> a -> a `mappend`Char -> Builder char7 Char '.'Builder -> Builder -> Builder forall a. Monoid a => a -> a -> a `mappend`[Builder] -> Builder mk0 [Builder] rs f t n [Builder] s []=t -> [Builder] -> [Builder] -> Builder f (t n t -> t -> t forall a. Num a => a -> a -> a -t 1)(Char -> Builder char7 Char '0'Builder -> [Builder] -> [Builder] forall a. a -> [a] -> [a] :[Builder] s )[]f t n [Builder] s (Builder r :[Builder] rs )=t -> [Builder] -> [Builder] -> Builder f (t n t -> t -> t forall a. Num a => a -> a -> a -t 1)(Builder r Builder -> [Builder] -> [Builder] forall a. a -> [a] -> [a] :[Builder] s )[Builder] rs inInt -> [Builder] -> [Builder] -> Builder forall {t}. (Eq t, Num t) => t -> [Builder] -> [Builder] -> Builder f Int e []([Int] -> [Builder] digitsToBuilder [Int] ds )JustInt p |Int e Int -> Int -> Bool forall a. Ord a => a -> a -> Bool >=Int 0->let(Int ei ,[Int] is' )=Int -> Int -> [Int] -> (Int, [Int]) roundToInt 10(Int p' Int -> Int -> Int forall a. Num a => a -> a -> a +Int e )[Int] ds ([Builder] ls ,[Builder] rs )=Int -> [Builder] -> ([Builder], [Builder]) forall a. Int -> [a] -> ([a], [a]) splitAt(Int e Int -> Int -> Int forall a. Num a => a -> a -> a +Int ei )([Int] -> [Builder] digitsToBuilder [Int] is' )in[Builder] -> Builder mk0 [Builder] ls Builder -> Builder -> Builder forall a. Monoid a => a -> a -> a `mappend`[Builder] -> Builder mkDot [Builder] rs |Bool otherwise->let(Int ei ,[Int] is' )=Int -> Int -> [Int] -> (Int, [Int]) roundToInt 10Int p' (Int -> Int -> [Int] forall a. Int -> a -> [a] replicate(-Int e )Int 0[Int] -> [Int] -> [Int] forall a. [a] -> [a] -> [a] ++[Int] ds )-- ds' should always be non-empty but use redundant pattern-- matching to silence warningds' :: [Int] ds' =ifInt ei Int -> Int -> Bool forall a. Ord a => a -> a -> Bool >Int 0then[Int] is' elseInt 0Int -> [Int] -> [Int] forall a. a -> [a] -> [a] :[Int] is' ([Builder] ls ,[Builder] rs )=Int -> [Builder] -> ([Builder], [Builder]) forall a. Int -> [a] -> ([a], [a]) splitAtInt 1([Builder] -> ([Builder], [Builder])) -> [Builder] -> ([Builder], [Builder]) forall a b. (a -> b) -> a -> b $[Int] -> [Builder] digitsToBuilder [Int] ds' in[Builder] -> Builder mk0 [Builder] ls Builder -> Builder -> Builder forall a. Monoid a => a -> a -> a `mappend`[Builder] -> Builder mkDot [Builder] rs wherep' :: Int p' =Int -> Int -> Int forall a. Ord a => a -> a -> a maxInt p Int 0wheremk0 :: [Builder] -> Builder mk0 [Builder] ls =case[Builder] ls of[]->Char -> Builder char7 Char '0';[Builder] _->[Builder] -> Builder forall a. Monoid a => [a] -> a mconcat[Builder] ls mkDot :: [Builder] -> Builder mkDot [Builder] rs =if[Builder] -> Bool forall a. [a] -> Bool forall (t :: * -> *) a. Foldable t => t a -> Bool null[Builder] rs thenBuilder forall a. Monoid a => a memptyelseChar -> Builder char7 Char '.'Builder -> Builder -> Builder forall a. Monoid a => a -> a -> a `mappend`[Builder] -> Builder forall a. Monoid a => [a] -> a mconcat[Builder] rs ds :: [Int] ds =Word64 -> [Int] digits Word64 m digitsToBuilder :: [Int] -> [Builder] digitsToBuilder =(Int -> Builder) -> [Int] -> [Builder] forall a b. (a -> b) -> [a] -> [b] forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b fmap(Char -> Builder char7 (Char -> Builder) -> (Int -> Char) -> Int -> Builder forall b c a. (b -> c) -> (a -> b) -> a -> c .Int -> Char intToDigit)