formatn (empty) → 0.0.1
raw patch · 5 files changed
+328/−0 lines, 5 filesdep +attoparsecdep +basedep +containerssetup-changed
Dependencies added: attoparsec, base, containers, doctest, foldl, formatn, generic-lens, numhask, scientific, tdigest, text, transformers
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- formatn.cabal +64/−0
- src/Data/FormatN.hs +218/−0
- test/test.hs +14/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Tony Day (c) 2017++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Tony Day nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ formatn.cabal view
@@ -0,0 +1,64 @@+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.++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+ location: https://github.com/tonyday567/formatn++library+ 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++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
+ src/Data/FormatN.hs view
@@ -0,0 +1,218 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RebindableSyntax #-}+{-# LANGUAGE TypeFamilies #-}+{-# OPTIONS_GHC -Wall #-}+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}+{-# OPTIONS_GHC -fno-warn-type-defaults #-}++-- | Formatting of 'Double's.+module Data.FormatN+ ( FormatN (..),+ defaultFormatN,+ fromFormatN,+ toFormatN,+ fixed,+ decimal,+ prec,+ comma,+ expt,+ dollar,+ formatN,+ precision,+ formatNs,+ )+where++import Data.Generics.Labels ()+import Data.List (nub)+import Data.Scientific+import qualified Data.Text as Text+import NumHask.Prelude++-- | Wrapper for the various formatting options.+--+-- >>> defaultFormatN+-- FormatComma 2+data FormatN+ = FormatFixed Int+ | FormatDecimal Int+ | FormatComma Int+ | FormatExpt Int+ | FormatPrec Int+ | FormatDollar Int+ | FormatPercent Int+ | FormatNone+ deriving (Eq, Show, Generic)++-- | The official format+defaultFormatN :: FormatN+defaultFormatN = FormatComma 2++-- | make text+formatN :: FormatN -> Double -> Text+formatN (FormatFixed n) x = fixed n x+formatN (FormatDecimal n) x = decimal n x+formatN (FormatPrec n) x = prec n x+formatN (FormatComma n) x = comma n x+formatN (FormatExpt n) x = expt n x+formatN (FormatDollar n) x = dollar n x+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"++-- | 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++-- | to x decimal places+--+-- >>> fixed 2 1+-- "1.00"+--+-- >>> fixed 2 0.001+-- "0.00"+fixed :: Int -> Double -> Text+fixed x n = pack $ formatScientific Fixed (Just x) (fromFloatDigits n)++-- | scientific exponential+--+-- >>> expt 2 1234+-- "1.23e3"+expt :: Int -> Double -> Text+expt x n = pack $ formatScientific Exponent (Just x) (fromFloatDigits n)++-- | round to n significant figures+--+-- >>> roundSig 2 1234+-- 1230.0+--+-- >>> roundSig 2 0.001234+-- 1.23e-3+roundSig :: Int -> Double -> Scientific+roundSig n x = scientific r' (e - length ds0)+ 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++-- | format numbers between 0.001 and 1,000,000 using digit and comma notation and exponential outside this range, with x significant figures.+--+-- >>> prec 1 0.00234+-- "0.0023"+--+-- >>> prec 1 0.000023+-- "2.3e-5"+--+-- >>> prec 1 123+-- "120"+--+-- >>> prec 1 123456+-- "120000"+--+-- >>> prec 1 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''+ where+ x' = pack $ formatScientific Fixed Nothing $ maybe fromFloatDigits roundSig (Just n) x+ x'' = (\x -> bool x' (fst x) (snd x == ".0")) $ Text.breakOn "." x'++-- | add commas format for numbers above 1,000 but below 1 million, otherwise use prec.+--+-- >>> comma 2 1234+-- "1,230"+comma :: Int -> Double -> Text+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++-- | dollars and cents, always decimal notation+--+-- >>> dollar 2 1234+-- "$1,230"+--+-- >>> dollar 2 0.01234+-- "$0.0123"+dollar :: Int -> Double -> Text+dollar n x+ | x < 0 = "-" <> dollar n (- x)+ | otherwise = "$" <> comma n x++-- | fixed percent, always decimal notation+--+-- >>> percent 2 0.001234+-- "0.123%"+percent :: 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+ 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'++-- | Consistently format a list of doubles.+formatNs :: FormatN -> [Double] -> [Text]+formatNs (FormatFixed n) xs = precision fixed n xs+formatNs (FormatDecimal n) xs = precision decimal n xs+formatNs (FormatPrec n) xs = precision prec n xs+formatNs (FormatComma n) xs = precision comma n xs+formatNs (FormatExpt n) xs = precision expt n xs+formatNs (FormatDollar n) xs = precision dollar n xs+formatNs (FormatPercent n) xs = precision percent n xs+formatNs FormatNone xs = pack . show <$> xs
+ test/test.hs view
@@ -0,0 +1,14 @@+{-# 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"+ ]