formatn 0.0.1 → 0.1.0
raw patch · 4 files changed
+339/−200 lines, 4 filesdep −attoparsecdep −doctestdep −foldldep ~containersdep ~textsetup-changedPVP ok
version bump matches the API change (PVP)
Dependencies removed: attoparsec, doctest, foldl, formatn, generic-lens, numhask, scientific, tdigest, transformers
Dependency ranges changed: containers, text
API changes (from Hackage documentation)
- Data.FormatN: fromFormatN :: IsString s => FormatN -> s
- Data.FormatN: toFormatN :: (Eq s, IsString s) => s -> Int -> FormatN
+ Data.FormatN: SigFig :: SigFigSign -> Integer -> Int -> SigFig
+ Data.FormatN: SigFigNeg :: SigFigSign
+ Data.FormatN: SigFigPos :: SigFigSign
+ Data.FormatN: [exponent] :: SigFig -> Int
+ Data.FormatN: [figures] :: SigFig -> Integer
+ Data.FormatN: [sign] :: SigFig -> SigFigSign
+ Data.FormatN: data SigFig
+ Data.FormatN: data SigFigSign
+ Data.FormatN: fromSigFig :: SigFig -> Double
+ Data.FormatN: instance GHC.Classes.Eq Data.FormatN.SigFig
+ Data.FormatN: instance GHC.Classes.Eq Data.FormatN.SigFigSign
+ Data.FormatN: instance GHC.Show.Show Data.FormatN.SigFig
+ Data.FormatN: instance GHC.Show.Show Data.FormatN.SigFigSign
+ Data.FormatN: percent :: Maybe Int -> Double -> Text
+ Data.FormatN: showOr :: FormatN -> Double -> Text
+ Data.FormatN: toSigFig :: Maybe Int -> Double -> SigFig
- Data.FormatN: FormatComma :: Int -> FormatN
+ Data.FormatN: FormatComma :: Maybe Int -> FormatN
- Data.FormatN: FormatDecimal :: Int -> FormatN
+ Data.FormatN: FormatDecimal :: Maybe Int -> FormatN
- Data.FormatN: FormatDollar :: Int -> FormatN
+ Data.FormatN: FormatDollar :: Maybe Int -> FormatN
- Data.FormatN: FormatExpt :: Int -> FormatN
+ Data.FormatN: FormatExpt :: Maybe Int -> FormatN
- Data.FormatN: FormatFixed :: Int -> FormatN
+ Data.FormatN: FormatFixed :: Maybe Int -> FormatN
- Data.FormatN: FormatPercent :: Int -> FormatN
+ Data.FormatN: FormatPercent :: Maybe Int -> FormatN
- Data.FormatN: FormatPrec :: Int -> FormatN
+ Data.FormatN: FormatPrec :: Maybe Int -> FormatN
- Data.FormatN: comma :: Int -> Double -> Text
+ Data.FormatN: comma :: Maybe Int -> Double -> Text
- Data.FormatN: decimal :: Int -> Double -> Text
+ Data.FormatN: decimal :: Maybe Int -> Double -> Text
- Data.FormatN: dollar :: Int -> Double -> Text
+ Data.FormatN: dollar :: Maybe Int -> Double -> Text
- Data.FormatN: expt :: Int -> Double -> Text
+ Data.FormatN: expt :: Maybe Int -> Double -> Text
- Data.FormatN: fixed :: Int -> Double -> Text
+ Data.FormatN: fixed :: Maybe Int -> Double -> Text
- Data.FormatN: prec :: Int -> Double -> Text
+ Data.FormatN: prec :: Maybe Int -> Double -> Text
- Data.FormatN: precision :: (Int -> Double -> Text) -> Int -> [Double] -> [Text]
+ Data.FormatN: precision :: (Maybe Int -> Double -> Text) -> Maybe Int -> [Double] -> [Text]
Files
- Setup.hs +1/−0
- formatn.cabal +35/−54
- src/Data/FormatN.hs +303/−132
- test/test.hs +0/−14
Setup.hs view
@@ -1,2 +1,3 @@ import Distribution.Simple+ main = defaultMain
formatn.cabal view
@@ -1,64 +1,45 @@-cabal-version: 2.4-name: formatn-version: 0.0.1-synopsis: Number text formatting.-description: This package provides support for common number formatting patterns.- .- == Usage- .- > import Data.FormatN- .- See doctests for functionality and range.+cabal-version: 2.4+name: formatn+version: 0.1.0+synopsis: Formatting of doubles.+description:+ This package provides support for number formatting styles, especially styles involving significant figure calculations.+ .+ == Usage+ .+ >>> import Data.FormatN+ >>> comma (Just 3) 1234+ 1,230 -category: Development-homepage: https://github.com/tonyday567/formatn#readme-bug-reports: https://github.com/tonyday567/formatn/issues-author: Tony Day-maintainer: tonyday567@gmail.com-copyright: 2016 Tony Day-license: BSD-3-Clause-license-file: LICENSE-build-type: Simple+category: Development+homepage: https://github.com/tonyday567/formatn#readme+bug-reports: https://github.com/tonyday567/formatn/issues+author: Tony Day+maintainer: tonyday567@gmail.com+copyright: 2016 Tony Day+license: BSD-3-Clause+license-file: LICENSE+build-type: Simple source-repository head- type: git+ type: git location: https://github.com/tonyday567/formatn library- hs-source-dirs:- src+ hs-source-dirs: src default-extensions: ghc-options:- -Wall- -Wcompat- -Wincomplete-record-updates- -Wincomplete-uni-patterns- -Wredundant-constraints- build-depends:- attoparsec >= 0.13 && < 0.14,- base >=4.7 && <5,- containers >= 0.6,- foldl >= 1.4,- generic-lens >= 1.2 && < 3.0,- numhask >= 0.7 && < 0.8,- scientific >= 0.3,- tdigest >= 0.2,- text >= 1.2,- transformers >= 0.5- exposed-modules:- Data.FormatN- other-modules:- default-language: Haskell2010+ -Wall -Wcompat -Wincomplete-record-updates+ -Wincomplete-uni-patterns -Wredundant-constraints -fwrite-ide-info+ -hiedir=.hie -test-suite test- type: exitcode-stdio-1.0- main-is: test.hs- hs-source-dirs:- test- default-extensions: build-depends:- base >=4.7 && <5,- doctest >= 0.16 && < 0.18,- formatn,- numhask >= 0.7 && < 0.8,- default-language: Haskell2010+ , base >=4.7 && <5+ , containers ^>=0.6+ , text ^>=1.2++ -- uncomment to run cabal-docspec --check-properties+ -- QuickCheck,+ exposed-modules: Data.FormatN+ other-modules:+ default-language: Haskell2010
src/Data/FormatN.hs view
@@ -1,63 +1,153 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-}-{-# LANGUAGE RebindableSyntax #-}-{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE StrictData #-} {-# OPTIONS_GHC -Wall #-}-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}-{-# OPTIONS_GHC -fno-warn-type-defaults #-} --- | Formatting of 'Double's.+-- | Text formatting of 'Double's.+--+-- In particular, the library provides functionality to calculate and display a fixed number of <https://en.wikipedia.org/wiki/Significant_figures significant figures> for a variety of different number formatting styles.+--+--+-- Some similar libraries that may be better suited for different use cases include:+--+-- Flexible formatters. These libraries provide more flexibility around formatting options, but do not have a concept of significance:+--+-- - <https://hackage.haskell.org/package/base-4.16.0.0/docs/Text-Printf.html Text.Printf> and <https://hackage.haskell.org/package/base-4.16.0.0/docs/Numeric.html#v:showFFloat Numeric> in base.+-- - <https://hackage.haskell.org/package/formatting Formatting>+-- - <https://hackage.haskell.org/package/vformat-0.9.0.0 vformat: A Python str.format() like formatter>+--+-- <https://hackage.haskell.org/package/text-format text-format> has similar functionality but is not native haskell and I wanted to do some tweaking to defaults. It's probably safer and faster.+--+-- <https://hackage.haskell.org/package/rounded rounded> seems to be much more about doing computation taking rounding into account, compared with the much simpler task of pretty printing a number.+--+-- This library could have just provided an ability to compute a significant figure version of a number and then use these other libraries, but the round trip (from Double to SigFig to Double) introduces errors (eg the least significant figure goes from being a '4' to a '3999999' via float maths). module Data.FormatN- ( FormatN (..),+ ( -- * Usage+ -- $usage++ -- * FormatN+ FormatN (..), defaultFormatN,- fromFormatN,- toFormatN,+ formatN,+ formatNs,+ precision,++ -- * SigFig+ SigFig (..),+ SigFigSign (..),+ toSigFig,+ fromSigFig,++ -- * formatters fixed,+ expt, decimal, prec, comma,- expt, dollar,- formatN,- precision,- formatNs,+ percent,+ showOr, ) where -import Data.Generics.Labels ()-import Data.List (nub)-import Data.Scientific+import Data.Bifunctor+import Data.Bool+import Data.Containers.ListUtils (nubOrd)+import Data.Foldable+import Data.Maybe+import Data.Text (Text, pack) import qualified Data.Text as Text-import NumHask.Prelude+import GHC.Generics hiding (prec)+import Numeric+import Prelude +-- $setup+-- >>> import Data.FormatN+-- >>> xs = [(-1),0,1,1.01,1.02,1.1,1.2]+--+-- >>> fixed (Just 2) <$> xs+-- ["-1.00","0.00","1.00","1.01","1.02","1.10","1.20"]+--+-- >>> decimal (Just 2) <$> xs+-- ["-1.0","0.0","1.0","1.0","1.0","1.1","1.2"]+--+-- >>> comma (Just 3) <$> xs+-- ["-1.00","0.00","1.00","1.01","1.02","1.10","1.20"]+--+-- comma (Just 3) . (1e3*) <$> xs+-- ["-1,000","0.00","1,000","1,010","1,020","1,100","1,200"]+--+-- comma (Just 3) . (1e-3*) <$> xs+-- ["-0.00100","0.00","0.00100","0.00101","0.00102","0.00110","0.00120"]+--+-- >>> comma (Just 3) . (1e-6*) <$> xs+-- ["-1.00e-6","0.00","1.00e-6","1.01e-6","1.02e-6","1.10e-6","1.20e-6"]+--+-- >>> comma (Just 2) . (1e3*) <$> xs+-- ["-1,000","0.0","1,000","1,000","1,000","1,100","1,200"]+--+-- >>> precision comma (Just 2) $ (1e3*) <$> [0,1,1.01,1.02,1.1,1.2]+-- ["0.00","1,000","1,010","1,020","1,100","1,200"]++-- $usage+-- >>> import Data.FormatN+-- >>> xs = [(-1),0,1,1.01,1.02,1.1,1.2]+-- >>> fixed (Just 2) <$> xs+-- ["-1.00","0.00","1.00","1.01","1.02","1.10","1.20"]+--+-- >>> decimal (Just 2) <$> xs+-- ["-1.0","0.0","1.0","1.0","1.0","1.1","1.2"]+--+-- formatn is used in the <https://hackage.haskell.org/package/chart-svg chart-svg> library to automate consistent number formatting across different scales.+--+-- >>> comma (Just 3) <$> xs+-- ["-1.00","0.00","1.00","1.01","1.02","1.10","1.20"]+--+-- >>> comma (Just 3) . (1e3*) <$> xs+-- ["-1,000","0.00","1,000","1,010","1,020","1,100","1,200"]+--+-- >>> comma (Just 3) . (1e-3*) <$> xs+-- ["-0.00100","0.00","0.00100","0.00101","0.00102","0.00110","0.00120"]+--+-- >>> comma (Just 3) . (1e-6*) <$> xs+-- ["-1.00e-6","0.00","1.00e-6","1.01e-6","1.02e-6","1.10e-6","1.20e-6"]+--+-- Using significant figures actually changes numbers - numbers that were slightly different end up being (and looking like) the same. 'precision' increases the number of significant figures to get around this.+--+-- >>> comma (Just 2) . (1e3*) <$> xs+-- ["-1,000","0.0","1,000","1,000","1,000","1,100","1,200"]+--+-- >>> precision comma (Just 2) $ (1e3*) <$> [0,1,1.01,1.02,1.1,1.2]+-- ["0.00","1,000","1,010","1,020","1,100","1,200"]+--+-- Also note the clunkiness of the treatment of zero. It is problematic to default format zero consistently.+ -- | Wrapper for the various formatting options. ----- >>> defaultFormatN--- FormatComma 2+-- Nothing in the context of these constructors means do not perform and significant figure adjustments to the numbers (or decimal figures with respect to FormatFixed). data FormatN- = FormatFixed Int- | FormatDecimal Int- | FormatComma Int- | FormatExpt Int- | FormatPrec Int- | FormatDollar Int- | FormatPercent Int+ = FormatFixed (Maybe Int)+ | FormatDecimal (Maybe Int)+ | FormatComma (Maybe Int)+ | FormatExpt (Maybe Int)+ | FormatPrec (Maybe Int)+ | FormatDollar (Maybe Int)+ | FormatPercent (Maybe Int) | FormatNone deriving (Eq, Show, Generic) -- | The official format+--+-- >>> defaultFormatN+-- FormatComma (Just 2) defaultFormatN :: FormatN-defaultFormatN = FormatComma 2+defaultFormatN = FormatComma (Just 2) --- | make text+-- | run a 'FormatN'+--+-- >>> formatN defaultFormatN 1234+-- "1,200" formatN :: FormatN -> Double -> Text formatN (FormatFixed n) x = fixed n x formatN (FormatDecimal n) x = decimal n x@@ -68,145 +158,219 @@ formatN (FormatPercent n) x = percent n x formatN FormatNone x = pack (show x) --- | type textifier-fromFormatN :: (IsString s) => FormatN -> s-fromFormatN (FormatFixed _) = "Fixed"-fromFormatN (FormatDecimal _) = "Decimal"-fromFormatN (FormatComma _) = "Comma"-fromFormatN (FormatExpt _) = "Expt"-fromFormatN (FormatPrec _) = "Prec"-fromFormatN (FormatDollar _) = "Dollar"-fromFormatN (FormatPercent _) = "Percent"-fromFormatN FormatNone = "None"+-- | Format to x decimal places with no significant figure rounding.+--+-- >>> fixed (Just 2) 100+-- "100.00"+--+-- >>> fixed (Just 2) 0.001+-- "0.00"+fixed :: Maybe Int -> Double -> Text+fixed n x = pack $ showFFloat n x "" --- | type readifier-toFormatN :: (Eq s, IsString s) => s -> Int -> FormatN-toFormatN "Fixed" n = FormatFixed n-toFormatN "Decimal" n = FormatDecimal n-toFormatN "Comma" n = FormatComma n-toFormatN "Expt" n = FormatExpt n-toFormatN "Prec" n = FormatPrec n-toFormatN "Dollar" n = FormatDollar n-toFormatN "Percent" n = FormatPercent n-toFormatN "None" _ = FormatNone-toFormatN _ _ = FormatNone+-- | Decomposition of a Double into the components that are needed to determine significant figure formatting.+--+-- eliding type changes, the relationship between a Double and a SigFig is:+--+-- \[+-- x == sign * figures * 10^{exponent}+-- \]+data SigFig = SigFig+ { -- | sign+ sign :: SigFigSign,+ -- | significant figures expressed as an Integer+ figures :: Integer,+ -- | the power of 10 exponent given figures.+ exponent :: Int+ }+ deriving (Eq, Show) --- | to x decimal places+-- | Sign component+data SigFigSign = SigFigNeg | SigFigPos deriving (Eq, Show)++sfsign :: SigFigSign -> String+sfsign s = bool "" "-" (s == SigFigNeg)++-- | convert from a Double to a 'SigFig' ----- >>> fixed 2 1--- "1.00"+-- >>> toSigFig (Just 2) 1234+-- SigFig {sign = SigFigPos, figures = 12, exponent = 2} ----- >>> fixed 2 0.001--- "0.00"-fixed :: Int -> Double -> Text-fixed x n = pack $ formatScientific Fixed (Just x) (fromFloatDigits n)+-- prop> \x -> let (SigFig s fs e) = toSigFig Nothing x in let x' = ((if (s==SigFigNeg) then (-1.0) else 1.0) * fromIntegral fs * 10.0**fromIntegral e) in (x==0 || abs (x/x'-1) < 1e-6)+toSigFig :: Maybe Int -> Double -> SigFig+toSigFig n x = SigFig s fs' expo+ where+ (s, (floatfs, floate)) = bool (SigFigPos, floatToDigits 10 x) (SigFigNeg, floatToDigits 10 (-x)) (x < 0)+ -- floatToDigits 10 0 == ([0],0) floatToDigits 10 1 == ([1],1)+ floate' = bool floate (floate + 1) (x == 0)+ nsig = fromMaybe (length floatfs) n+ -- pad with extra zeros if less figures than requested+ (floatfs', e) =+ bool+ (floatfs, floate' - length floatfs)+ (floatfs <> replicate (nsig - length floatfs) 0, floate' - nsig)+ (length floatfs < nsig)+ (fs0, fs1) = splitAt nsig floatfs'+ -- reconstitute number to get rounding right at the least significance point+ fs =+ round $+ (fromIntegral $ foldl' (\x' a -> x' * 10 + a) 0 fs0 :: Double)+ + fromIntegral (foldl' (\x' a -> x' * 10 + a) 0 fs1) / (10.0 ^ (length fs1 :: Int))+ -- rounding can bump significant figures by 1 eg 99(.9999) ==> 100+ (fs', expo) =+ bool+ (fs, e + length floatfs' - nsig)+ (fs `div` 10, e + length floatfs' - nsig + 1)+ (length (show fs) > nsig) --- | scientific exponential+-- | convert from a 'SigFig' to a Double ----- >>> expt 2 1234--- "1.23e3"-expt :: Int -> Double -> Text-expt x n = pack $ formatScientific Exponent (Just x) (fromFloatDigits n)+-- >>> fromSigFig (SigFig SigFigPos 12 2)+-- 1200.0+--+-- @fromSigFig . toSigFig Nothing@ may not be isomorphic+fromSigFig :: SigFig -> Double+fromSigFig (SigFig s fs e) = bool 1 (-1) (s == SigFigNeg) * fromIntegral fs * 10 ** fromIntegral e --- | round to n significant figures+exptR :: SigFig -> Text+exptR (SigFig s i e) = pack $ sfsign s <> i'' <> "e" <> show e'+ where+ i''+ | length i' == 1 = i'+ | otherwise = take 1 i' <> "." <> drop 1 i'+ i' = show i+ e' = e + length i' - 1++-- | Format in exponential style, maybe with significant figure rounding. ----- >>> roundSig 2 1234--- 1230.0+-- >>> expt Nothing 1245+-- "1.245e3" ----- >>> roundSig 2 0.001234--- 1.23e-3-roundSig :: Int -> Double -> Scientific-roundSig n x = scientific r' (e - length ds0)+-- >>> expt (Just 3) 1245+-- "1.24e3"+--+-- >>> expt (Just 3) 0.1245+-- "1.24e-1"+expt :: Maybe Int -> Double -> Text+expt n x = exptR (toSigFig n x)++decimalR :: SigFig -> Text+decimalR (SigFig s xs e) = pack $ sfsign s <> t where- (ds, e) = toDecimalDigits $ fromFloatDigits x- (ds0, ds1) = splitAt (n + 1) ds- r =- (fromIntegral $ foldl' (\x a -> x * 10 + a) 0 ds0 :: Double)- + fromIntegral (foldl' (\x a -> x * 10 + a) 0 ds1) / (10.0 ^ (length ds1 :: Int))- r' = round r :: Integer+ xs' = show xs+ nsf = length xs'+ extrasf = bool (-(e + nsf)) (-(e + nsf)) (xs == 0)+ oversf = length xs' + e+ t+ | e >= 0 = xs' <> replicate e '0'+ | e <= -nsf = "0." <> replicate extrasf '0' <> xs'+ | otherwise = take oversf xs' <> "." <> drop oversf xs' --- | format numbers between 0.001 and 1,000,000 using digit and comma notation and exponential outside this range, with x significant figures.+-- | Format in decimal style, and maybe round to n significant figures. ----- >>> prec 1 0.00234+-- >>> decimal Nothing 1.2345e-2+-- "0.012345"+--+-- >>> decimal (Just 2) 0.012345+-- "0.012"+--+-- >>> decimal (Just 2) 12345+-- "12000"+decimal :: Maybe Int -> Double -> Text+decimal n x = decimalR (toSigFig n x)++-- | Format between 0.001 and 1,000,000 using decimal style and exponential style outside this range.+--+-- >>> prec (Just 2) 0.00234 -- "0.0023" ----- >>> prec 1 0.000023+-- >>> prec (Just 2) 0.000023 -- "2.3e-5" ----- >>> prec 1 123+-- >>> prec (Just 2) 123 -- "120" ----- >>> prec 1 123456+-- >>> prec (Just 2) 123456 -- "120000" ----- >>> prec 1 1234567+-- >>> prec (Just 2) 1234567 -- "1.2e6"-prec :: Int -> Double -> Text-prec n x- | x < 0 = "-" <> prec n (- x)- | x == 0 = "0"- | x < 0.001 = expt n x- | x > 1e6 = expt n x- | otherwise = decimal n (toRealFloat x')- where- x' = maybe fromFloatDigits roundSig (Just n) x---- | round to n significant figures and always use decimal notation------ >>> decimal 2 0.000001234--- "0.00000123"------ >>> decimal 2 1234567--- "1230000"-decimal :: Int -> Double -> Text-decimal n x = x''+prec :: Maybe Int -> Double -> Text+prec n x = bool (go x) ("-" <> go (-x)) (x < 0) where- x' = pack $ formatScientific Fixed Nothing $ maybe fromFloatDigits roundSig (Just n) x- x'' = (\x -> bool x' (fst x) (snd x == ".0")) $ Text.breakOn "." x'+ go x' =+ bool+ ( bool+ (decimal n x')+ (expt n x')+ (x' > 1e6)+ )+ (expt n x')+ (x' < 0.001 && (x /= 0)) --- | add commas format for numbers above 1,000 but below 1 million, otherwise use prec.+-- | Format using comma separators for numbers above 1,000 but below 1 million, otherwise use prec style. ----- >>> comma 2 1234+-- >>> comma (Just 3) 1234 -- "1,230"-comma :: Int -> Double -> Text+comma :: Maybe Int -> Double -> Text comma n x- | x < 0 = "-" <> comma n (- x)+ | x < 0 = "-" <> comma n (-x) | x < 1000 || x > 1e6 = prec n x | otherwise = addcomma (prec n x) where- addcomma x = uncurry (<>) . first (Text.reverse . Text.intercalate "," . Text.chunksOf 3 . Text.reverse) $ Text.breakOn "." x+ addcomma =+ uncurry (<>)+ . first (Text.reverse . Text.intercalate "," . Text.chunksOf 3 . Text.reverse)+ . Text.breakOn "." --- | dollars and cents, always decimal notation+-- | Format as dollars, always using comma notation ----- >>> dollar 2 1234+-- >>> dollar (Just 3) 1234 -- "$1,230" ----- >>> dollar 2 0.01234--- "$0.0123"-dollar :: Int -> Double -> Text+-- >>> dollar (Just 2) 0.01234+-- "$0.012"+dollar :: Maybe Int -> Double -> Text dollar n x- | x < 0 = "-" <> dollar n (- x)+ | x < 0 = "-" <> dollar n (-x) | otherwise = "$" <> comma n x --- | fixed percent, always decimal notation+-- | Format as a percentage using decimal style. ----- >>> percent 2 0.001234--- "0.123%"-percent :: Int -> Double -> Text+-- >>> percent (Just 2) 0.001234+-- "0.12%"+percent :: Maybe Int -> Double -> Text percent n x = (<> "%") $ decimal n (100 * x) --- | Provide formatted text for a list of numbers so that they are just distinguished. 'precision commas 2 ticks' means use as much precision as is needed for them to be distinguished, but with at least 2 significant figures.-precision :: (Int -> Double -> Text) -> Int -> [Double] -> [Text]-precision f n0 xs =- precLoop f n0 xs+precision_ :: (Maybe Int -> Double -> Text) -> Int -> [Double] -> [Text]+precision_ f n0 xs =+ precLoop n0 xs where- precLoop f' n xs' =- let s = f' n <$> xs'- in if s == nub s || n > 4- then s- else precLoop f' (n + 1) xs'+ precLoop n xs' =+ let s = f (Just n) <$> xs'+ in bool (precLoop (1 + n) xs') s (s == nubOrd s || n > 4) --- | Consistently format a list of doubles.+-- | Provide formatted text for a list of numbers so that they are just distinguished.+--+-- For example, __@precision comma (Just 2)@__ means use as much significant figures as is needed for the numbers to be distinguished on rendering, but with at least 2 significant figures.+--+-- The difference between this and __@fmap (comma (Just 2))@__ can be seen in these examples:+--+-- >>> precision comma (Just 2) [0,1,1.01,1.02,1.1,1.2]+-- ["0.00","1.00","1.01","1.02","1.10","1.20"]+--+-- >>> fmap (comma (Just 2)) [0,1,1.01,1.02,1.1,1.2]+-- ["0.0","1.0","1.0","1.0","1.1","1.2"]+precision :: (Maybe Int -> Double -> Text) -> Maybe Int -> [Double] -> [Text]+precision f n xs =+ case n of+ Nothing -> f Nothing <$> xs+ Just n' -> precision_ f n' xs++-- | Consistently format a list of numbers via using 'precision'.+--+-- >>> formatNs defaultFormatN [0,1,1.01,1.02,1.1,1.2]+-- ["0.00","1.00","1.01","1.02","1.10","1.20"] formatNs :: FormatN -> [Double] -> [Text] formatNs (FormatFixed n) xs = precision fixed n xs formatNs (FormatDecimal n) xs = precision decimal n xs@@ -216,3 +380,10 @@ formatNs (FormatDollar n) xs = precision dollar n xs formatNs (FormatPercent n) xs = precision percent n xs formatNs FormatNone xs = pack . show <$> xs++-- | Format with the shorter of show and formatN.+showOr :: FormatN -> Double -> Text+showOr f x = bool (bool f' (pack s') (Text.length (pack s') < Text.length f')) "0" (x < 1e-6 && x > -1e-6)+ where+ f' = formatN f x+ s' = show x
− test/test.hs
@@ -1,14 +0,0 @@-{-# LANGUAGE RebindableSyntax #-}-{-# OPTIONS_GHC -Wall #-}-{-# OPTIONS_GHC -fno-warn-unused-imports #-}--module Main where--import NumHask.Prelude-import Test.DocTest--main :: IO ()-main =- doctest- [ "src/Data/FormatN.hs"- ]