|
| 1 | +-- | |
| 2 | +-- Module: Functora.Round |
| 3 | +-- |
| 4 | +-- Rounding rationals to significant digits and decimal places. |
| 5 | +-- |
| 6 | +-- The 'round' function from the prelude returns an integer. The standard librarys |
| 7 | +-- of C and C++ have round functions that return floating point numbers. Rounding |
| 8 | +-- in this library takes and returns 'Rational's and can round to a number of |
| 9 | +-- significant digits or a number of decimal places. |
| 10 | +module Functora.Round |
| 11 | + ( -- * About the Name |
| 12 | + -- $name |
| 13 | + |
| 14 | + -- * Rounding to Decimal Places |
| 15 | + dpRound, |
| 16 | + |
| 17 | + -- * Rounding to Significant Digits |
| 18 | + sdRound, |
| 19 | + ) |
| 20 | +where |
| 21 | + |
| 22 | +import Numeric.Natural (Natural) |
| 23 | +import Prelude |
| 24 | + |
| 25 | +-- | Rounds to a non-negative number of __d__ecimal __p__laces. After rounding |
| 26 | +-- the result would have the given number of decimal places if converted to |
| 27 | +-- a floating point number, such as by using 'fromRational'. |
| 28 | +-- |
| 29 | +-- >>> dpRound 2 (1234.56789 :: Rational) |
| 30 | +-- 123457 % 100 |
| 31 | +-- >>> dpRound 2 (123456789 :: Rational) |
| 32 | +-- 123456789 % 1 |
| 33 | +-- |
| 34 | +-- Some examples that may be easier to read using decimal point notation. |
| 35 | +-- |
| 36 | +-- >>> dpRound 2 (123456789 :: Rational) == (123456789 :: Rational) |
| 37 | +-- True |
| 38 | +-- >>> dpRound 2 (1234.56789 :: Rational) == (1234.57 :: Rational) |
| 39 | +-- True |
| 40 | +-- >>> dpRound 2 (123.456789 :: Rational) == (123.46 :: Rational) |
| 41 | +-- True |
| 42 | +-- >>> dpRound 2 (12.3456789 :: Rational) == (12.35 :: Rational) |
| 43 | +-- True |
| 44 | +-- >>> dpRound 2 (1.23456789 :: Rational) == (1.23 :: Rational) |
| 45 | +-- True |
| 46 | +-- >>> dpRound 2 (0.123456789 :: Rational) == (0.12 :: Rational) |
| 47 | +-- True |
| 48 | +-- >>> dpRound 2 (0.0123456789 :: Rational) == (0.01 :: Rational) |
| 49 | +-- True |
| 50 | +-- >>> dpRound 2 (0.0000123456789 :: Rational) == (0.0 :: Rational) |
| 51 | +-- True |
| 52 | +-- |
| 53 | +-- If the required number of decimal places is less than zero it is taken to |
| 54 | +-- be zero. |
| 55 | +-- |
| 56 | +-- >>> dpRound 0 (1234.56789 :: Rational) |
| 57 | +-- 1235 % 1 |
| 58 | +-- >>> dpRound (-1) (1234.56789 :: Rational) |
| 59 | +-- 1235 % 1 |
| 60 | +-- >>> dpRound 0 (123456789 :: Rational) |
| 61 | +-- 123456789 % 1 |
| 62 | +-- >>> dpRound (-1) (123456789 :: Rational) |
| 63 | +-- 123456789 % 1 |
| 64 | +-- |
| 65 | +-- Rounding to the existing number of decimal places or more makes no |
| 66 | +-- difference. |
| 67 | +-- |
| 68 | +-- >>> 1234.56789 :: Rational |
| 69 | +-- 123456789 % 100000 |
| 70 | +-- >>> dpRound 5 (1234.56789 :: Rational) |
| 71 | +-- 123456789 % 100000 |
| 72 | +-- >>> dpRound 6 (1234.56789 :: Rational) |
| 73 | +-- 123456789 % 100000 |
| 74 | + |
| 75 | +-- SEE: https://stackoverflow.com/questions/12450501/round-number-to-specified-number-of-digits |
| 76 | +dpRound :: (RealFrac a) => Integer -> a -> a |
| 77 | +dpRound n f |
| 78 | + | n < 0 = dpRound 0 f |
| 79 | + | otherwise = |
| 80 | + fromInteger (round $ f * (10 ^ n)) / (10.0 ^^ n) |
| 81 | +{-# INLINEABLE dpRound #-} |
| 82 | +{-# SPECIALIZE dpRound :: Integer -> Double -> Double #-} |
| 83 | +{-# SPECIALIZE dpRound :: Integer -> Rational -> Rational #-} |
| 84 | + |
| 85 | +-- | Rounds to a non-negative number of __s__ignificant __d__igits. |
| 86 | +-- |
| 87 | +-- >>> sdRound 1 (123456789 :: Rational) |
| 88 | +-- 100000000 % 1 |
| 89 | +-- >>> sdRound 4 (123456789 :: Rational) |
| 90 | +-- 123500000 % 1 |
| 91 | +-- >>> sdRound 8 (1234.56789 :: Rational) |
| 92 | +-- 12345679 % 10000 |
| 93 | +-- |
| 94 | +-- More examples using decimal point notation. |
| 95 | +-- |
| 96 | +-- >>> sdRound 4 (123456789 :: Rational) == (123500000 :: Rational) |
| 97 | +-- True |
| 98 | +-- >>> sdRound 4 (1234.56789 :: Rational) == (1235 :: Rational) |
| 99 | +-- True |
| 100 | +-- >>> sdRound 4 (123.456789 :: Rational) == (123.5 :: Rational) |
| 101 | +-- True |
| 102 | +-- >>> sdRound 4 (12.3456789 :: Rational) == (12.35 :: Rational) |
| 103 | +-- True |
| 104 | +-- >>> sdRound 4 (1.23456789 :: Rational) == (1.235 :: Rational) |
| 105 | +-- True |
| 106 | +-- >>> sdRound 4 (0.123456789 :: Rational) == (0.1235 :: Rational) |
| 107 | +-- True |
| 108 | +-- >>> sdRound 4 (0.0123456789 :: Rational) == (0.01235 :: Rational) |
| 109 | +-- True |
| 110 | +-- >>> sdRound 4 (0.0000123456789 :: Rational) == (0.00001235 :: Rational) |
| 111 | +-- True |
| 112 | +-- |
| 113 | +-- Rounding to the existing number of significant digits or more makes no |
| 114 | +-- difference. |
| 115 | +-- |
| 116 | +-- >>> 1234.56789 :: Rational |
| 117 | +-- 123456789 % 100000 |
| 118 | +-- >>> sdRound 9 (1234.56789 :: Rational) |
| 119 | +-- 123456789 % 100000 |
| 120 | +-- >>> sdRound 10 (1234.56789 :: Rational) |
| 121 | +-- 123456789 % 100000 |
| 122 | +-- |
| 123 | +-- Rounding to zero significant digits is always zero. |
| 124 | +-- |
| 125 | +-- >>> sdRound 0 (123456789 :: Rational) |
| 126 | +-- 0 % 1 |
| 127 | +-- >>> sdRound 0 (1234.56789 :: Rational) |
| 128 | +-- 0 % 1 |
| 129 | +-- >>> sdRound 0 (0.123456789 :: Rational) |
| 130 | +-- 0 % 1 |
| 131 | +-- >>> sdRound 0 (0.0000123456789 :: Rational) |
| 132 | +-- 0 % 1 |
| 133 | +sdRound :: (RealFrac a) => Natural -> a -> a |
| 134 | +sdRound sd' f = |
| 135 | + if m < 0 |
| 136 | + then dpRound sd gZ / 10 ^^ pZ |
| 137 | + else case compare n 0 of |
| 138 | + EQ -> dpRound n f |
| 139 | + GT -> dpRound n f |
| 140 | + LT -> 10 ^^ p * fromInteger (round g) |
| 141 | + where |
| 142 | + sd = toInteger sd' |
| 143 | + |
| 144 | + f' :: Double |
| 145 | + f' = fromRational $ toRational f |
| 146 | + |
| 147 | + m :: Double |
| 148 | + m = logBase 10 f' |
| 149 | + mZ :: Integer |
| 150 | + mZ = truncate m |
| 151 | + |
| 152 | + n = sd - (mZ + 1) |
| 153 | + |
| 154 | + p = negate n |
| 155 | + pZ = negate mZ |
| 156 | + |
| 157 | + g = f / 10 ^ p |
| 158 | + gZ = f * 10 ^ pZ |
| 159 | +{-# INLINEABLE sdRound #-} |
| 160 | +{-# SPECIALIZE sdRound :: Natural -> Double -> Double #-} |
| 161 | +{-# SPECIALIZE sdRound :: Natural -> Rational -> Rational #-} |
| 162 | + |
| 163 | +-- $name |
| 164 | +-- Rounding to decimal places is a special case of rounding significant digits. |
| 165 | +-- When the number is split into whole and fractional parts, rounding to |
| 166 | +-- decimal places is rounding to significant digits in the fractional part. |
| 167 | +-- |
| 168 | +-- The digits that are discarded become dust and a digit when written down is |
| 169 | +-- a char. |
| 170 | +-- |
| 171 | +-- The package name is __siggy__ for significant digits and __chardust__ for |
| 172 | +-- the digits that are discarded. |
0 commit comments