readme-lhs-0.7.0: src/Readme/Format.hs
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RebindableSyntax #-}
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_GHC -Wno-type-defaults #-}
-- | formatting of numbers and statistics
module Readme.Format
( average,
deciles,
percentile,
fixed,
commas,
prec,
secs,
) where
import NumHask.Prelude
import Data.Scientific
import Data.TDigest
import qualified Data.Text as Text
-- | average
--
-- >>> average [0..1000]
-- 500.0
average :: (Foldable f) => f Double -> Double
average xs = sum xs / fromIntegral (length xs)
-- | compute deciles
--
-- >>> deciles 5 [1..1000]
-- [1.0,200.5,400.5,600.5000000000001,800.5,1000.0]
--
deciles :: (Foldable f) => Int -> f Double -> [Double]
deciles n xs =
( \x ->
fromMaybe 0 $
quantile x (tdigest xs :: TDigest 25)
)
<$> ((/ fromIntegral n) . fromIntegral <$> [0 .. n])
-- | compute a percentile
--
-- >>> percentile 0.1 [1..1000]
-- 100.5
--
percentile :: (Foldable f) => Double -> f Double -> Double
percentile p xs = fromMaybe 0 $ quantile p (tdigest xs :: TDigest 25)
-- | fixed
-- >>> fixed 2 22.256
-- "22.26"
fixed :: Int -> Double -> Text
fixed n x
| Text.length a == 0 = "0." <> (Text.replicate (n - Text.length b) "0") <> b
| Text.length b == 0 = a
| otherwise = a <> "." <> b
where
i = show (round (x * (10.0 ^ n)) :: Integer) :: Text
(a, b) = Text.splitAt (Text.length i - n) i
-- | format a Float as a Scientific with a precision
-- >>> prec 3 123456
-- "1.235e5"
prec :: Int -> Double -> Text
prec p x = sciprec p (fromFloatDigits x)
-- | format with 1000 separated commas, and a fixed decimal part
-- >>> commas 2 123456.789
-- "123,456.79"
commas :: Int -> Double -> Text
commas n a
| a < 0 = "-" <> commas n (- a)
| a < 1000.0 = fixed n a
| otherwise = intPart (floor a) "" <> decPart (a - fromInteger (floor a))
where
intPart :: Integer -> Text -> Text
intPart x t
| x < 1000 = Text.pack (show x) <> t
| otherwise =
let (d, m) = divMod x 1000
in intPart d ("," <> Text.pack (show m))
decPart :: Double -> Text
decPart x = Text.drop 1 $ fixed n x
sciprec :: Int -> Scientific -> Text
sciprec n x = Text.pack $ formatScientific Exponent (Just n) x
-- | format as seconds
-- >>> secs 2 0.0000002345
-- "234ns"
secs :: Int -> Double -> Text
secs p s
| s < 0.0 = "-" <> secs p (- s)
| s >= 1.0 = fixed p s <> "s"
| s >= 1e-3 = fixed 0 (s * 1e3) <> "ms"
| s >= 1e-6 = fixed 0 (s * 1e6) <> "μs"
| s >= 1e-9 = fixed 0 (s * 1e9) <> "ns"
| otherwise = fixed p (s * 1e12) <> "ps"