{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_HADDOCK prune #-}
module Data.SigFig.Util where
import Data.BigDecimal (BigDecimal (..))
import Data.BigDecimal qualified as BD
import Data.SigFig.Types hiding (div)
import Data.Text (Text)
import Data.Text qualified as T
import GHC.Natural (naturalFromInteger)
import GHC.Real (Ratio ((:%)), (%))
rightmostSignificantPlace :: Integer -> BigDecimal -> Integer
rightmostSignificantPlace :: Integer -> BigDecimal -> Integer
rightmostSignificantPlace Integer
sf BigDecimal
bd =
let v' :: BigDecimal
v' = BigDecimal -> BigDecimal
BD.nf BigDecimal
bd
dp :: Natural
dp = BigDecimal -> Natural
BD.scale BigDecimal
v'
p :: Natural
p = BigDecimal -> Natural
BD.precision BigDecimal
v'
in Natural -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral Natural
p Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
sf Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Natural -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral Natural
dp
forceDP :: Integer -> BigDecimal -> Term
forceDP :: Integer -> BigDecimal -> Term
forceDP Integer
dp BigDecimal
bd =
let res :: BigDecimal
res = BigDecimal -> BigDecimal
BD.nf (BigDecimal -> BigDecimal) -> BigDecimal -> BigDecimal
forall a b. (a -> b) -> a -> b
$ BigDecimal -> Integer -> BigDecimal
roundToPlace BigDecimal
bd Integer
dp
in Integer -> BigDecimal -> Term
Measured (Integer -> Integer -> Integer
forall a. Ord a => a -> a -> a
max Integer
0 (Integer -> Integer) -> Integer -> Integer
forall a b. (a -> b) -> a -> b
$ Natural -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral (BigDecimal -> Natural
BD.precision BigDecimal
res) Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Natural -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral (BigDecimal -> Natural
BD.scale BigDecimal
res) Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
dp) BigDecimal
res
forceSF :: Integer -> BigDecimal -> Term
forceSF :: Integer -> BigDecimal -> Term
forceSF Integer
sf' BigDecimal
bd = Integer -> BigDecimal -> Term
Measured Integer
sf' (BigDecimal -> Term)
-> (BigDecimal -> BigDecimal) -> BigDecimal -> Term
forall b c a. (b -> c) -> (a -> b) -> a -> c
. BigDecimal -> Integer -> BigDecimal
roundToPlace BigDecimal
bd (Integer -> BigDecimal)
-> (BigDecimal -> Integer) -> BigDecimal -> BigDecimal
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Integer -> BigDecimal -> Integer
rightmostSignificantPlace Integer
sf' (BigDecimal -> Term) -> BigDecimal -> Term
forall a b. (a -> b) -> a -> b
$ BigDecimal
bd
roundToPlace :: BigDecimal -> Integer -> BigDecimal
roundToPlace :: BigDecimal -> Integer -> BigDecimal
roundToPlace bd :: BigDecimal
bd@(BigDecimal Integer
v Natural
s) Integer
dp
| Integer
dp Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
< Integer
0 = BigDecimal -> RoundingAdvice -> BigDecimal
BD.roundBD BigDecimal
bd (RoundingAdvice -> BigDecimal) -> RoundingAdvice -> BigDecimal
forall a b. (a -> b) -> a -> b
$ Natural -> RoundingAdvice
BD.halfUp (Integer -> Natural
forall a b. (Integral a, Num b) => a -> b
fromIntegral (- Integer
dp))
| Bool
otherwise =
let bd' :: BigDecimal
bd' = Integer -> Natural -> BigDecimal
BigDecimal Integer
v (Natural
s Natural -> Natural -> Natural
forall a. Num a => a -> a -> a
+ Integer -> Natural
forall a b. (Integral a, Num b) => a -> b
fromIntegral Integer
dp)
in BigDecimal -> RoundingAdvice -> BigDecimal
BD.roundBD BigDecimal
bd' (Natural -> RoundingAdvice
BD.halfUp Natural
0) BigDecimal -> BigDecimal -> BigDecimal
forall a. Num a => a -> a -> a
* BigDecimal
10 BigDecimal -> Integer -> BigDecimal
forall a b. (Num a, Integral b) => a -> b -> a
^ Integer
dp
display :: Term -> Text
display :: Term -> Text
display (Measured Integer
sf BigDecimal
bd) = BigDecimal -> Text
format BigDecimal
bd
where
ssf :: Text
ssf = String -> Text
T.pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ Integer -> String
forall a. Show a => a -> String
show Integer
sf
format :: BigDecimal -> Text
format :: BigDecimal -> Text
format BigDecimal
term' =
let term :: BigDecimal
term@(BigDecimal Integer
v Natural
s') = BigDecimal -> BigDecimal
BD.nf BigDecimal
term'
s :: Integer
s = Natural -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral Natural
s' :: Integer
termText :: Text
termText = String -> Text
T.pack (String -> Text) -> (BigDecimal -> String) -> BigDecimal -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. BigDecimal -> String
forall a. Show a => a -> String
show (BigDecimal -> Text) -> BigDecimal -> Text
forall a b. (a -> b) -> a -> b
$ BigDecimal
term
p :: Integer
p = Natural -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral (BigDecimal -> Natural
BD.precision BigDecimal
term) :: Integer
rsdp :: Integer
rsdp = Integer
p Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
sf Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
s
rsd :: Integer
rsd = if Integer
sf Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
p then Integer
0 else Integer
v Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
`div` (Integer
10 Integer -> Integer -> Integer
forall a b. (Num a, Integral b) => a -> b -> a
^ (Integer
rsdp Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
s)) Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
`mod` Integer
10
in if Integer
rsd Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
/= Integer
0 Bool -> Bool -> Bool
|| Integer
rsdp Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
0 Bool -> Bool -> Bool
&& Integer
p Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
1
then Text
termText
else
if Integer
rsdp Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
1
then let coef :: BigDecimal
coef = Integer -> Natural -> BigDecimal
BigDecimal Integer
v (Integer -> Natural
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Integer
s Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ (Integer
p Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
1))) in BigDecimal -> Text
format BigDecimal
coef Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" x 10^" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (Integer -> String
forall a. Show a => a -> String
show (Integer -> String) -> Integer -> String
forall a b. (a -> b) -> a -> b
$ Integer
p Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
1)
else
Text
termText
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (if Integer
s Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
0 then Text
"" else Text
".")
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text -> Text
T.replicate (Integer -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Integer -> Int) -> Integer -> Int
forall a b. (a -> b) -> a -> b
$ Integer
sf Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
p) Text
"0"
display (Constant v :: Rational
v@(Integer
a :% Integer
b)) =
String -> Text
T.pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$
if Integer -> Bool
isTerminating Integer
b
then BigDecimal -> String
forall a. Show a => a -> String
show (BigDecimal -> String)
-> (BigDecimal -> BigDecimal) -> BigDecimal -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. BigDecimal -> BigDecimal
BD.nf (BigDecimal -> String) -> BigDecimal -> String
forall a b. (a -> b) -> a -> b
$ Rational -> BigDecimal
forall a. Fractional a => Rational -> a
fromRational Rational
v
else Integer -> String
forall a. Show a => a -> String
show Integer
a String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"/" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Integer -> String
forall a. Show a => a -> String
show Integer
b
displayFull :: Term -> Text
displayFull :: Term -> Text
displayFull t :: Term
t@(Measured Integer
sf BigDecimal
bd) = Term -> Text
display Term
t Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
annot
where
annot :: Text
annot = Text
" (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (Integer -> String
forall a. Show a => a -> String
show Integer
sf) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" s.f.)"
displayFull t :: Term
t@(Constant (Integer
a :% Integer
b)) = Term -> Text
display Term
t Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
annot
where
annot :: Text
annot = if Integer -> Bool
isTerminating Integer
b then Text
" (const)" else Text
" (non-terminating const)"
displayInformational :: Term -> (Text, Text)
displayInformational :: Term -> (Text, Text)
displayInformational t :: Term
t@(Measured Integer
sf BigDecimal
bd) = (Term -> Text
display Term
t, Text
annot)
where
annot :: Text
annot = String -> Text
T.pack (Integer -> String
forall a. Show a => a -> String
show Integer
sf) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" significant figure" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> if Integer
sf Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
/= Integer
1 then Text
"s" else Text
forall a. Monoid a => a
mempty
displayInformational t :: Term
t@(Constant (Integer
a :% Integer
b)) = (Term -> Text
display Term
t, Text
annot)
where
annot :: Text
annot = if Integer -> Bool
isTerminating Integer
b then Text
"constant value" else Text
"constant, non-terminating decimal value"
isTerminating :: Integer -> Bool
isTerminating :: Integer -> Bool
isTerminating = (Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
1) (Integer -> Bool) -> (Integer -> Integer) -> Integer -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
stripFactor Integer
5 (Integer -> Integer) -> (Integer -> Integer) -> Integer -> Integer
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Integer -> Integer -> Integer
forall a. Integral a => a -> a -> a
stripFactor Integer
2
where
stripFactor :: p -> p -> p
stripFactor p
d p
n = case p
n p -> p -> (p, p)
forall a. Integral a => a -> a -> (a, a)
`quotRem` p
d of
(p
q, p
0) -> p -> p -> p
stripFactor p
d p
q
(p, p)
_ -> p
n