Skip to content

Commit 00c66a7

Browse files
added shortest format mode to RealFloat that prints the shortest possible string
1 parent 67c4cb4 commit 00c66a7

File tree

5 files changed

+89
-1
lines changed

5 files changed

+89
-1
lines changed

Data/ByteString/Builder/RealFloat.hs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,12 @@ module Data.ByteString.Builder.RealFloat
7878
, standardDefaultPrecision
7979
, scientific
8080
, generic
81+
, shortest
8182
) where
8283

8384
import Data.ByteString.Builder.Internal (Builder)
8485
import qualified Data.ByteString.Builder.RealFloat.Internal as R
85-
import Data.ByteString.Builder.RealFloat.Internal (FloatFormat(..), fScientific, fGeneric)
86+
import Data.ByteString.Builder.RealFloat.Internal (FloatFormat(..), fScientific, fGeneric, fShortest, SpecialStrings(SpecialStrings))
8687
import Data.ByteString.Builder.RealFloat.Internal (positiveZero, negativeZero)
8788
import qualified Data.ByteString.Builder.RealFloat.F2S as RF
8889
import qualified Data.ByteString.Builder.RealFloat.D2S as RD
@@ -162,6 +163,18 @@ standardSpecialStrings = scientificSpecialStrings
162163
generic :: FloatFormat
163164
generic = fGeneric 'e' Nothing (0,7) standardSpecialStrings
164165

166+
-- | Standard or scientific notation depending on which uses the least number of charabers.
167+
--
168+
-- @since ????
169+
shortest :: FloatFormat
170+
shortest = fShortest 'e' SpecialStrings
171+
{ nan = "NaN"
172+
, positiveInfinity = "Inf"
173+
, negativeInfinity = "-Inf"
174+
, positiveZero = "0"
175+
, negativeZero = "-0"
176+
}
177+
165178
-- TODO: support precision argument for FGeneric and FScientific
166179
-- | Returns a rendered Float. Returns the \'shortest\' representation in
167180
-- scientific notation and takes an optional precision argument in standard
@@ -235,6 +248,7 @@ formatFloating :: forall a mw ew ei.
235248
, R.Mantissa mw
236249
, ToWord64 mw
237250
, R.DecimalLength mw
251+
, BuildDigits mw
238252
-- exponent
239253
, ew ~ R.ExponentWord a
240254
, Integral ew
@@ -251,6 +265,16 @@ formatFloating fmt f = case fmt of
251265
else sci eE
252266
FScientific {..} -> specialsOr specials $ sci eE
253267
FStandard {..} -> specialsOr specials $ std precision
268+
FShortest {..} -> specialsOr specials
269+
if e'' >= 0 && (olength + 2 >= e'' || olength == 1 && e'' <= 2)
270+
|| e'' < 0 && (olength + e'' >= (-3) || olength == 1 && e'' >= (-2))
271+
then if e'' >= 0
272+
then printSign f <> buildDigits (truncate $ abs f :: mw)
273+
else std Nothing
274+
else sci eE
275+
where
276+
e'' = R.toInt e
277+
olength = R.decimalLength m
254278
where
255279
sci eE = BP.primBounded (R.toCharsScientific @a Proxy eE sign m e) ()
256280
std precision = printSign f <> showStandard (toWord64 m) e' precision
@@ -316,3 +340,7 @@ showStandard m e prec =
316340
ds = digits m
317341
digitsToBuilder = fmap (char7 . intToDigit)
318342

343+
class BuildDigits a where buildDigits :: a -> Builder
344+
instance BuildDigits Word32 where buildDigits = BP.primBounded BP.word32Dec
345+
instance BuildDigits Word64 where buildDigits = BP.primBounded BP.word64Dec
346+

Data/ByteString/Builder/RealFloat/Internal.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ module Data.ByteString.Builder.RealFloat.Internal
8989
, FloatFormat(..)
9090
, fScientific
9191
, fGeneric
92+
, fShortest
9293

9394
, module Data.ByteString.Builder.RealFloat.TableGenerator
9495
) where
@@ -1001,6 +1002,10 @@ data FloatFormat
10011002
, stdExpoRange :: (Int, Int)
10021003
, specials :: SpecialStrings
10031004
}
1005+
| FShortest
1006+
{ eE :: Word8#
1007+
, specials :: SpecialStrings
1008+
}
10041009
deriving Show
10051010
fScientific :: Char -> SpecialStrings -> FloatFormat
10061011
fScientific eE specials = FScientific
@@ -1012,3 +1017,8 @@ fGeneric eE precision stdExpoRange specials = FGeneric
10121017
{ eE = asciiRaw $ ord eE
10131018
, ..
10141019
}
1020+
fShortest :: Char -> SpecialStrings -> FloatFormat
1021+
fShortest eE specials = FShortest
1022+
{ eE = asciiRaw $ ord eE
1023+
, ..
1024+
}

bench/BenchAll.hs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,22 @@ main = do
409409
, benchB "Double Average" doubleSpecials $ foldMap (formatDouble standardDefaultPrecision)
410410
]
411411
]
412+
, bgroup "FShortest"
413+
[ bgroup "Positive"
414+
[ benchB "Float" floatPosData $ foldMap (formatFloat shortest)
415+
, benchB "Double" doublePosData $ foldMap (formatDouble shortest)
416+
, benchB "DoubleSmall" doublePosSmallData $ foldMap (formatDouble shortest)
417+
]
418+
, bgroup "Negative"
419+
[ benchB "Float" floatNegData $ foldMap (formatFloat shortest)
420+
, benchB "Double" doubleNegData $ foldMap (formatDouble shortest)
421+
, benchB "DoubleSmall" doubleNegSmallData $ foldMap (formatDouble shortest)
422+
]
423+
, bgroup "Special"
424+
[ benchB "Float Average" floatSpecials $ foldMap (formatFloat shortest)
425+
, benchB "Double Average" doubleSpecials $ foldMap (formatDouble shortest)
426+
]
427+
]
412428
]
413429
]
414430
]

bytestring.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ test-suite bytestring-tests
181181
deepseq,
182182
ghc-prim,
183183
QuickCheck,
184+
quickcheck-assertions,
184185
tasty,
185186
tasty-hunit,
186187
tasty-quickcheck >= 0.8.1,

tests/builder/Data/ByteString/Builder/Tests.hs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import Test.Tasty.QuickCheck
6161
, (===), (.&&.), conjoin
6262
, UnicodeString(..), NonNegative(..)
6363
)
64+
import Test.QuickCheck.Assertions ((?<=))
6465
import QuickCheckUtils
6566

6667

@@ -743,6 +744,22 @@ testsFloating = testGroup "RealFloat"
743744
, ( 1.2345678 , "1.2345678" )
744745
, ( 1.23456735e-36 , "1.23456735e-36" )
745746
]
747+
, testGroup "FShortest"
748+
[ testProperty "prints equivalent value" \f -> read (LC.unpack $ toLazyByteString $ formatFloat shortest f) === f
749+
, testProperty "shortest length always less than or equal to standard or scientific length outputs" \f -> let
750+
sh = L.length $ toLazyByteString $ formatFloat shortest f
751+
std = L.length $ toLazyByteString $ formatFloat standardDefaultPrecision f
752+
sci = L.length $ toLazyByteString $ formatFloat scientific f
753+
in sh ?<= min std sci
754+
, testMatches "no .0 for whole numbers" (formatFloat shortest) (show . truncate)
755+
[ (1, "1")
756+
, (-1, "-1")
757+
, (10, "10")
758+
, (-10, "-10")
759+
, (15, "15")
760+
, (-15, "-15")
761+
]
762+
]
746763
, testMatches "f2sPowersOf10" floatDec show $
747764
fmap asShowRef [read ("1.0e" ++ show x) :: Float | x <- [-46..39 :: Int]]
748765
]
@@ -973,6 +990,22 @@ testsFloating = testGroup "RealFloat"
973990
, ( 549755813888.0e+3 , "5.49755813888e14" )
974991
, ( 8796093022208.0e+3 , "8.796093022208e15" )
975992
]
993+
, testGroup "FShortest"
994+
[ testProperty "prints equivalent value" \f -> read (LC.unpack $ toLazyByteString $ formatDouble shortest f) === f
995+
, testProperty "shortest length always less than or equal to standard or scientific length outputs" \f -> let
996+
sh = L.length $ toLazyByteString $ formatDouble shortest f
997+
std = L.length $ toLazyByteString $ formatDouble standardDefaultPrecision f
998+
sci = L.length $ toLazyByteString $ formatDouble scientific f
999+
in sh ?<= min std sci
1000+
, testMatches "no .0 for whole numbers" (formatDouble shortest) (show . truncate)
1001+
[ (1, "1")
1002+
, (-1, "-1")
1003+
, (10, "10")
1004+
, (-10, "-10")
1005+
, (15, "15")
1006+
, (-15, "-15")
1007+
]
1008+
]
9761009
, testMatches "d2sPowersOf10" doubleDec show $
9771010
fmap asShowRef [read ("1.0e" ++ show x) :: Double | x <- [-324..309 :: Int]]
9781011
]

0 commit comments

Comments
 (0)