-
Notifications
You must be signed in to change notification settings - Fork 143
Implement floating point conversion with ryu #365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 34 commits
ba61414
7d80321
356ed6c
c0a0583
ff2210d
0bb5a01
de1d174
8dd7f16
23d5cfe
755f58f
4635e2b
76b5e2e
b5f7086
648bfae
d172abf
5f3dce5
f1c6275
c2c2c87
f6497c2
60db980
6ec7e2d
a73c84e
3ccfd47
6b3eaaa
abf6e04
f771cd5
b394896
9815597
c0648bb
d87d3ae
5500d59
7d7d7fa
906d6db
046a42b
8fafed4
dde95e2
415ac6f
0474332
9be8170
f67df50
a01cb00
0cc5417
12436a2
d8dac2a
b70918b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| -- | | ||
| -- Module : Data.ByteString.Builder.RealFloat | ||
| -- Copyright : (c) Lawrence Wu 2021 | ||
| -- License : BSD-style | ||
| -- Maintainer : [email protected] | ||
| -- | ||
| -- Floating point formatting for Bytestring.Builder | ||
| -- | ||
| -- This module primarily exposes `floatDec` and `doubleDec` which do the | ||
| -- equivalent of converting through `string7 . show`. | ||
0xlwu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| -- | ||
| -- Experimentally, it also exposes `formatFloat` and `formatDouble` which | ||
| -- accept the same arguments as `GHC.Float.formatRealFloat`. Currently, | ||
| -- precision is not supported for the general and scientific notation versions | ||
| -- of this API. | ||
| -- | ||
0xlwu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| module Data.ByteString.Builder.RealFloat | ||
sjakobi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ( FloatFormat(..) | ||
| , floatDec | ||
| , doubleDec | ||
| , formatFloat | ||
| , formatDouble | ||
| ) where | ||
|
|
||
| import Data.ByteString.Builder.Internal (Builder) | ||
| import qualified Data.ByteString.Builder.RealFloat.Internal as R | ||
| import qualified Data.ByteString.Builder.RealFloat.F2S as RF | ||
| import qualified Data.ByteString.Builder.RealFloat.D2S as RD | ||
| import qualified Data.ByteString.Builder.Prim as BP | ||
| import GHC.Float (roundTo) | ||
| import GHC.Word (Word32, Word64) | ||
| import GHC.Show (intToDigit) | ||
|
|
||
| -- | Returns a rendered Float. Matches `show` in displaying in fixed or | ||
| -- scientific notation | ||
0xlwu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| {-# INLINABLE floatDec #-} | ||
| floatDec :: Float -> Builder | ||
| floatDec = formatFloat FGeneric Nothing | ||
|
|
||
| -- | Returns a rendered Double. Matches `show` in displaying in fixed or | ||
| -- scientific notation | ||
| {-# INLINABLE doubleDec #-} | ||
| doubleDec :: Double -> Builder | ||
| doubleDec = formatDouble FGeneric Nothing | ||
|
|
||
| -- | ByteString float-to-string format | ||
| data FloatFormat | ||
| = FExponent -- ^ scientific notation | ||
0xlwu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| | FFixed -- ^ fixed precision with `Maybe Int` digits after the decimal | ||
| | FGeneric -- ^ dispatches to fixed or exponent based on the exponent | ||
sjakobi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| deriving Show | ||
0xlwu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| -- TODO: support precision argument for FGeneric and FExponent | ||
sjakobi marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| -- | Returns a rendered Float. Matches the API of `formatRealFloat` but does | ||
| -- not currently handle the precision argument in case of `FGeneric` and | ||
| -- `FExponent` | ||
0xlwu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| -- | ||
| -- e.g | ||
| -- | ||
| -- > formatFloat FFixed (Just 2) 12.345 = "12.34" | ||
| -- > formatFloat FFixed (Just 5) 12.345 = "12.34500" | ||
0xlwu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| -- > formatFloat FGeneric Nothing 12.345 = "12.345" | ||
0xlwu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| {-# INLINABLE formatFloat #-} | ||
| formatFloat :: FloatFormat-> Maybe Int -> Float -> Builder | ||
0xlwu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| formatFloat fmt prec f = | ||
| case fmt of | ||
| FGeneric -> | ||
| case specialStr f of | ||
| Just b -> b | ||
| Nothing -> | ||
| if e' >= 0 && e' <= 7 | ||
| then sign f `mappend` showFixed (word32ToWord64 m) e' prec | ||
| else BP.primBounded (R.toCharsScientific (f < 0) m e) () | ||
| where (RF.FloatingDecimal m e) = RF.f2Intermediate f | ||
| e' = R.int32ToInt e + R.decimalLength9 m | ||
| word32ToWord64 :: Word32 -> Word64 | ||
| word32ToWord64 = fromIntegral | ||
| FExponent -> RF.f2s f | ||
| FFixed -> d2Fixed (realToFrac f) prec | ||
|
|
||
| -- TODO: support precision argument for FGeneric and FExponent | ||
| -- | Returns a rendered Double. Matches the API of `formatRealFloat` but does | ||
| -- not currently handle the precision argument in case of `FGeneric` and | ||
| -- `FExponent` | ||
0xlwu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| -- | ||
| -- e.g | ||
| -- | ||
| -- > formatDouble FFixed (Just 2) 12.345 = "12.34" | ||
| -- > formatDouble FFixed (Just 5) 12.345 = "12.34500" | ||
| -- > formatDouble FGeneric Nothing 12.345 = "12.345" | ||
0xlwu marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| {-# INLINABLE formatDouble #-} | ||
| formatDouble :: FloatFormat-> Maybe Int -> Double -> Builder | ||
| formatDouble fmt prec f = | ||
| case fmt of | ||
| FGeneric -> | ||
| case specialStr f of | ||
| Just b -> b | ||
| Nothing -> | ||
| if e' >= 0 && e' <= 7 | ||
| then sign f `mappend` showFixed m e' prec | ||
| else BP.primBounded (R.toCharsScientific (f < 0) m e) () | ||
| where (RD.FloatingDecimal m e) = RD.d2Intermediate f | ||
| e' = R.int32ToInt e + R.decimalLength17 m | ||
| FExponent -> RD.d2s f | ||
| FFixed -> d2Fixed f prec | ||
|
|
||
| -- | Show fixed floating point matching show / formatRealFloat output by | ||
| -- dropping digits after exponentiation precision | ||
| d2Fixed :: Double -> Maybe Int -> Builder | ||
| d2Fixed f prec = | ||
| case specialStr f of | ||
| Just b -> b | ||
| Nothing -> sign f `mappend` showFixed m e' prec | ||
| where (RD.FloatingDecimal m e) = RD.d2Intermediate f | ||
| -- NB: exponent in exponential format is e' - 1 | ||
| e' = R.int32ToInt e + R.decimalLength17 m :: Int | ||
|
|
||
| -- | Char7 encode a 'Char'. | ||
| {-# INLINE char7 #-} | ||
| char7 :: Char -> Builder | ||
| char7 = BP.primFixed BP.char7 | ||
|
|
||
| -- | Char7 encode a 'String'. | ||
| {-# INLINE string7 #-} | ||
| string7 :: String -> Builder | ||
| string7 = BP.primMapListFixed BP.char7 | ||
|
|
||
| -- | Encodes a `-` if input is negative | ||
| sign :: RealFloat a => a -> Builder | ||
| sign f = if f < 0 then char7 '-' else mempty | ||
|
|
||
| -- | Special rendering for Nan, Infinity, and 0. See | ||
| -- RealFloat.Internal.NonNumbersAndZero | ||
| specialStr :: RealFloat a => a -> Maybe Builder | ||
| specialStr f | ||
| | isNaN f = Just $ string7 "NaN" | ||
| | isInfinite f = Just $ sign f `mappend` string7 "Infinity" | ||
| | isNegativeZero f = Just $ string7 "-0.0" | ||
| | f == 0 = Just $ string7 "0.0" | ||
| | otherwise = Nothing | ||
|
|
||
| -- | Returns a list of decimal digits in a Word64 | ||
| digits :: Word64 -> [Int] | ||
| digits w = go [] w | ||
| where go ds 0 = ds | ||
| go ds c = let (q, r) = R.dquotRem10 c | ||
| in go ((R.word64ToInt r) : ds) q | ||
|
|
||
| -- | Show a floating point value in fixed point. Based on GHC.Float.showFloat | ||
| showFixed :: Word64 -> Int -> Maybe Int -> Builder | ||
| showFixed m e prec = | ||
| case prec of | ||
| Nothing | ||
| | e <= 0 -> char7 '0' | ||
| `mappend` char7 '.' | ||
| `mappend` string7 (replicate (-e) '0') | ||
| `mappend` mconcat (digitsToBuilder ds) | ||
| | otherwise -> | ||
| let f 0 s rs = mk0 (reverse s) `mappend` char7 '.' `mappend` mk0 rs | ||
| f n s [] = f (n-1) (char7 '0':s) [] | ||
| f n s (r:rs) = f (n-1) (r:s) rs | ||
| in f e [] (digitsToBuilder ds) | ||
| Just p | ||
| | e >= 0 -> | ||
| let (ei, is') = roundTo 10 (p' + e) ds | ||
| (ls, rs) = splitAt (e + ei) (digitsToBuilder is') | ||
| in mk0 ls `mappend` mkDot rs | ||
| | otherwise -> | ||
| let (ei, is') = roundTo 10 p' (replicate (-e) 0 ++ ds) | ||
| (b:bs) = digitsToBuilder (if ei > 0 then is' else 0:is') | ||
|
||
| in b `mappend` mkDot bs | ||
| where p' = max p 0 | ||
| where | ||
| mk0 ls = case ls of [] -> char7 '0'; _ -> mconcat ls | ||
| mkDot rs = if null rs then mempty else char7 '.' `mappend` mconcat rs | ||
| ds = digits m | ||
| digitsToBuilder = fmap (char7 . intToDigit) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.