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

AltStyle によって変換されたページ (->オリジナル) /