-- SPDX-License-Identifier: MPL-2.0

{- | Exact rational unit conversion and API-level dimensional safety. Opaque
quantities store SI values; addition/conversion reject incompatible dimensions.
Seven SI base exponents are augmented by data and plane-angle tags, so bytes
cannot be added to lengths and angles cannot be added to plain scalars.
Absolute temperatures and differences are distinct: absolute + absolute and
products of absolutes are rejected; subtracting absolutes produces a difference.
-}
module HMath.Units (
    Dimension,
    dimension,
    dimensionExponents,
    dimensionless,
    lengthDimension,
    massDimension,
    timeDimension,
    temperatureDimension,
    angleDimension,
    dataDimension,
    Unit,
    unit,
    unitName,
    unitDimension,
    unitScale,
    unitOffset,
    unitProduct,
    unitQuotient,
    unitPower,
    Quantity,
    quantity,
    temperatureDifference,
    quantityDimension,
    siValue,
    inUnit,
    convert,
    addQuantity,
    subtractQuantity,
    multiplyQuantity,
    divideQuantity,
    SIPrefix (..),
    siPrefixes,
    prefixed,
    metre,
    kilometre,
    centimetre,
    millimetre,
    inch,
    foot,
    yard,
    mile,
    nauticalMile,
    squareMetre,
    hectare,
    cubicMetre,
    litre,
    millilitre,
    kilogram,
    gram,
    tonne,
    pound,
    second,
    minute,
    hour,
    day,
    metresPerSecond,
    kilometresPerHour,
    milesPerHour,
    metresPerSecondSquared,
    newton,
    joule,
    watt,
    pascal,
    bar,
    atmosphere,
    kelvin,
    celsius,
    fahrenheit,
    hertz,
    bit,
    byte,
    kibibyte,
    mebibyte,
    gigabyte,
    radian,
    degree,
    gradian,
    radiansToDegrees,
    degreesToRadians,
    radiansToGradians,
    gradiansToRadians,
) where

import HMath.Constants (constantName, constantValue, mathematicalConstants)
import HMath.Error (MathError (..))

{- | Opaque nine-exponent dimensional witness: seven SI bases plus data and plane-angle
tags. Construct with dimension.
-}
newtype Dimension = Dimension [Integer] deriving (Dimension -> Dimension -> Bool
(Dimension -> Dimension -> Bool)
-> (Dimension -> Dimension -> Bool) -> Eq Dimension
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Dimension -> Dimension -> Bool
== :: Dimension -> Dimension -> Bool
$c/= :: Dimension -> Dimension -> Bool
/= :: Dimension -> Dimension -> Bool
Eq, Int -> Dimension -> ShowS
[Dimension] -> ShowS
Dimension -> String
(Int -> Dimension -> ShowS)
-> (Dimension -> String)
-> ([Dimension] -> ShowS)
-> Show Dimension
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Dimension -> ShowS
showsPrec :: Int -> Dimension -> ShowS
$cshow :: Dimension -> String
show :: Dimension -> String
$cshowList :: [Dimension] -> ShowS
showList :: [Dimension] -> ShowS
Show)

{- | Exponents in order length, mass, time, current, temperature, amount,
luminous intensity, data, plane angle. Require exactly nine exponents.
-}
dimension :: [Integer] -> Either MathError Dimension
dimension :: [Integer] -> Either MathError Dimension
dimension [Integer]
xs
    | [Integer] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Integer]
xs Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Int
9 = MathError -> Either MathError Dimension
forall a b. a -> Either a b
Left (String -> MathError
InvalidArgument String
"dimension requires nine exponents")
    | Bool
otherwise = Dimension -> Either MathError Dimension
forall a b. b -> Either a b
Right ([Integer] -> Dimension
Dimension [Integer]
xs)

{- | Return nine exact integer exponents ordered as length, mass, time, current,
temperature, amount, luminous intensity, data, plane angle.
-}
dimensionExponents :: Dimension -> [Integer]
dimensionExponents :: Dimension -> [Integer]
dimensionExponents (Dimension [Integer]
xs) = [Integer]
xs

dimensionless
    , lengthDimension
    , massDimension
    , timeDimension
    , temperatureDimension
    , angleDimension
    , dataDimension ::
        Dimension

{- | Dimension witness with all nine exponents zero; all unspecified exponents are zero. Use
to construct or compare dimension-safe units/quantities.
-}
dimensionless :: Dimension
dimensionless = [Integer] -> Dimension
Dimension (Int -> Integer -> [Integer]
forall a. Int -> a -> [a]
replicate Int
9 Integer
0)

{- | Dimension witness with length exponent one; all unspecified exponents are zero. Use to
construct or compare dimension-safe units/quantities.
-}
lengthDimension :: Dimension
lengthDimension = Int -> Dimension
base Int
0

{- | Dimension witness with mass exponent one; all unspecified exponents are zero. Use to
construct or compare dimension-safe units/quantities.
-}
massDimension :: Dimension
massDimension = Int -> Dimension
base Int
1

{- | Dimension witness with time exponent one; all unspecified exponents are zero. Use to
construct or compare dimension-safe units/quantities.
-}
timeDimension :: Dimension
timeDimension = Int -> Dimension
base Int
2

{- | Dimension witness with temperature exponent one; all unspecified exponents are zero.
Use to construct or compare dimension-safe units/quantities.
-}
temperatureDimension :: Dimension
temperatureDimension = Int -> Dimension
base Int
4

{- | Dimension witness with data tag exponent one; all unspecified exponents are zero. Use
to construct or compare dimension-safe units/quantities.
-}
dataDimension :: Dimension
dataDimension = Int -> Dimension
base Int
7

{- | Dimension witness with plane-angle tag exponent one; all unspecified exponents are
zero. Use to construct or compare dimension-safe units/quantities.
-}
angleDimension :: Dimension
angleDimension = Int -> Dimension
base Int
8

base :: Int -> Dimension
base :: Int -> Dimension
base Int
i = [Integer] -> Dimension
Dimension [if Int
i Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
j then Integer
1 else Integer
0 | Int
j <- [Int
0 .. Int
8]]
combine :: (Integer -> Integer -> Integer) -> Dimension -> Dimension -> Dimension
combine :: (Integer -> Integer -> Integer)
-> Dimension -> Dimension -> Dimension
combine Integer -> Integer -> Integer
op (Dimension [Integer]
a) (Dimension [Integer]
b) = [Integer] -> Dimension
Dimension ((Integer -> Integer -> Integer)
-> [Integer] -> [Integer] -> [Integer]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith Integer -> Integer -> Integer
op [Integer]
a [Integer]
b)
scaleDimension :: Integer -> Dimension -> Dimension
scaleDimension :: Integer -> Dimension -> Dimension
scaleDimension Integer
n (Dimension [Integer]
xs) = [Integer] -> Dimension
Dimension ((Integer -> Integer) -> [Integer] -> [Integer]
forall a b. (a -> b) -> [a] -> [b]
map (Integer
n Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
*) [Integer]
xs)

{- | Opaque exact affine unit definition SI=value*scale+offset. Nonzero offsets are
restricted to absolute-temperature units.
-}
data Unit = Unit String Dimension Rational Rational deriving (Unit -> Unit -> Bool
(Unit -> Unit -> Bool) -> (Unit -> Unit -> Bool) -> Eq Unit
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Unit -> Unit -> Bool
== :: Unit -> Unit -> Bool
$c/= :: Unit -> Unit -> Bool
/= :: Unit -> Unit -> Bool
Eq, Int -> Unit -> ShowS
[Unit] -> ShowS
Unit -> String
(Int -> Unit -> ShowS)
-> (Unit -> String) -> ([Unit] -> ShowS) -> Show Unit
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Unit -> ShowS
showsPrec :: Int -> Unit -> ShowS
$cshow :: Unit -> String
show :: Unit -> String
$cshowList :: [Unit] -> ShowS
showList :: [Unit] -> ShowS
Show)

-- | Return the display name/symbol of a Unit, for example "m". No conversion is performed.
unitName :: Unit -> String
unitName :: Unit -> String
unitName (Unit String
name Dimension
_ Rational
_ Rational
_) = String
name

{- | Return the dimensional-exponent witness of a Unit. Compatible conversions require equal
dimensions.
-}
unitDimension :: Unit -> Dimension
unitDimension :: Unit -> Dimension
unitDimension (Unit String
_ Dimension
dim Rational
_ Rational
_) = Dimension
dim

{- | Return exact Rational scale in the conversion SI = value*scale + offset. All public
Unit constructors require a positive scale.
-}
unitScale :: Unit -> Rational
unitScale :: Unit -> Rational
unitScale (Unit String
_ Dimension
_ Rational
scale Rational
_) = Rational
scale

{- | Return exact Rational affine offset in SI = value*scale + offset. Only temperature
units may have a nonzero offset.
-}
unitOffset :: Unit -> Rational
unitOffset :: Unit -> Rational
unitOffset (Unit String
_ Dimension
_ Rational
_ Rational
offset) = Rational
offset

-- | SI = value * scale + offset. Only temperature units may have an offset.
unit :: String -> Dimension -> Rational -> Rational -> Either MathError Unit
unit :: String
-> Dimension -> Rational -> Rational -> Either MathError Unit
unit String
name Dimension
dim Rational
scale Rational
offset
    | Rational
scale Rational -> Rational -> Bool
forall a. Ord a => a -> a -> Bool
<= Rational
0 = MathError -> Either MathError Unit
forall a b. a -> Either a b
Left (String -> MathError
InvalidArgument String
"unit scale must be positive")
    | Rational
offset Rational -> Rational -> Bool
forall a. Eq a => a -> a -> Bool
/= Rational
0 Bool -> Bool -> Bool
&& Dimension
dim Dimension -> Dimension -> Bool
forall a. Eq a => a -> a -> Bool
/= Dimension
temperatureDimension =
        MathError -> Either MathError Unit
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"affine offset requires temperature dimension")
    | Bool
otherwise = Unit -> Either MathError Unit
forall a b. b -> Either a b
Right (String -> Dimension -> Rational -> Rational -> Unit
Unit String
name Dimension
dim Rational
scale Rational
offset)

unitProduct, unitQuotient :: Unit -> Unit -> Either MathError Unit

{- | Multiply two zero-offset units: add dimension exponents and multiply Rational scales.
Any affine offset returns Left DomainError. This constructs a Unit, not a Quantity.
-}
unitProduct :: Unit -> Unit -> Either MathError Unit
unitProduct Unit
a Unit
b
    | Unit -> Rational
unitOffset Unit
a Rational -> Rational -> Bool
forall a. Eq a => a -> a -> Bool
/= Rational
0 Bool -> Bool -> Bool
|| Unit -> Rational
unitOffset Unit
b Rational -> Rational -> Bool
forall a. Eq a => a -> a -> Bool
/= Rational
0 = MathError -> Either MathError Unit
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"cannot multiply affine units")
    | Bool
otherwise =
        Unit -> Either MathError Unit
forall a b. b -> Either a b
Right
            ( String -> Dimension -> Rational -> Rational -> Unit
Unit
                (Unit -> String
unitName Unit
a String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" " String -> ShowS
forall a. [a] -> [a] -> [a]
++ Unit -> String
unitName Unit
b)
                ((Integer -> Integer -> Integer)
-> Dimension -> Dimension -> Dimension
combine Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
(+) (Unit -> Dimension
unitDimension Unit
a) (Unit -> Dimension
unitDimension Unit
b))
                (Unit -> Rational
unitScale Unit
a Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
* Unit -> Rational
unitScale Unit
b)
                Rational
0
            )

{- | Divide two zero-offset units: subtract dimension exponents and divide their positive
scales. Affine units fail with DomainError.
-}
unitQuotient :: Unit -> Unit -> Either MathError Unit
unitQuotient Unit
a Unit
b
    | Unit -> Rational
unitOffset Unit
a Rational -> Rational -> Bool
forall a. Eq a => a -> a -> Bool
/= Rational
0 Bool -> Bool -> Bool
|| Unit -> Rational
unitOffset Unit
b Rational -> Rational -> Bool
forall a. Eq a => a -> a -> Bool
/= Rational
0 = MathError -> Either MathError Unit
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"cannot divide affine units")
    | Bool
otherwise =
        Unit -> Either MathError Unit
forall a b. b -> Either a b
Right
            ( String -> Dimension -> Rational -> Rational -> Unit
Unit
                (Unit -> String
unitName Unit
a String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"/" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Unit -> String
unitName Unit
b)
                ((Integer -> Integer -> Integer)
-> Dimension -> Dimension -> Dimension
combine (-) (Unit -> Dimension
unitDimension Unit
a) (Unit -> Dimension
unitDimension Unit
b))
                (Unit -> Rational
unitScale Unit
a Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/ Unit -> Rational
unitScale Unit
b)
                Rational
0
            )

{- | Given zero-offset Unit then Integer exponent, multiply its exponents and raise its
positive scale to that power. Negative powers are valid; affine units fail.
-}
unitPower :: Unit -> Integer -> Either MathError Unit
unitPower :: Unit -> Integer -> Either MathError Unit
unitPower Unit
a Integer
n
    | Unit -> Rational
unitOffset Unit
a Rational -> Rational -> Bool
forall a. Eq a => a -> a -> Bool
/= Rational
0 = MathError -> Either MathError Unit
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"cannot exponentiate affine units")
    | Bool
otherwise =
        Unit -> Either MathError Unit
forall a b. b -> Either a b
Right (String -> Dimension -> Rational -> Rational -> Unit
Unit (Unit -> String
unitName Unit
a String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"^" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Integer -> String
forall a. Show a => a -> String
show Integer
n) (Integer -> Dimension -> Dimension
scaleDimension Integer
n (Unit -> Dimension
unitDimension Unit
a)) (Unit -> Rational
unitScale Unit
a Rational -> Integer -> Rational
forall a b. (Fractional a, Integral b) => a -> b -> a
^^ Integer
n) Rational
0)

{- | Opaque exact SI-valued dimensional quantity,tagging absolute temperatures separately
from differences. Construct with quantity or temperatureDifference.
-}
data Quantity = Quantity Dimension Rational Bool deriving (Quantity -> Quantity -> Bool
(Quantity -> Quantity -> Bool)
-> (Quantity -> Quantity -> Bool) -> Eq Quantity
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Quantity -> Quantity -> Bool
== :: Quantity -> Quantity -> Bool
$c/= :: Quantity -> Quantity -> Bool
/= :: Quantity -> Quantity -> Bool
Eq, Int -> Quantity -> ShowS
[Quantity] -> ShowS
Quantity -> String
(Int -> Quantity -> ShowS)
-> (Quantity -> String) -> ([Quantity] -> ShowS) -> Show Quantity
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Quantity -> ShowS
showsPrec :: Int -> Quantity -> ShowS
$cshow :: Quantity -> String
show :: Quantity -> String
$cshowList :: [Quantity] -> ShowS
showList :: [Quantity] -> ShowS
Show)

{- | Given Unit then Rational value, construct an exact SI-valued Quantity using its scale
and offset. A temperature unit constructs an absolute temperature; use
temperatureDifference for differences.
-}
quantity :: Unit -> Rational -> Quantity
quantity :: Unit -> Rational -> Quantity
quantity Unit
u Rational
x =
    Dimension -> Rational -> Bool -> Quantity
Quantity
        (Unit -> Dimension
unitDimension Unit
u)
        (Rational
x Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
* Unit -> Rational
unitScale Unit
u Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
+ Unit -> Rational
unitOffset Unit
u)
        (Unit -> Dimension
unitDimension Unit
u Dimension -> Dimension -> Bool
forall a. Eq a => a -> a -> Bool
== Dimension
temperatureDimension)

{- | Given temperature Unit then Rational difference, multiply by its scale while ignoring
its affine offset. A non-temperature unit returns Left DomainError. The result is not
an absolute temperature.
-}
temperatureDifference :: Unit -> Rational -> Either MathError Quantity
temperatureDifference :: Unit -> Rational -> Either MathError Quantity
temperatureDifference Unit
u Rational
x
    | Unit -> Dimension
unitDimension Unit
u Dimension -> Dimension -> Bool
forall a. Eq a => a -> a -> Bool
/= Dimension
temperatureDimension =
        MathError -> Either MathError Quantity
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"temperature difference requires temperature unit")
    | Bool
otherwise = Quantity -> Either MathError Quantity
forall a b. b -> Either a b
Right (Dimension -> Rational -> Bool -> Quantity
Quantity Dimension
temperatureDimension (Rational
x Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
* Unit -> Rational
unitScale Unit
u) Bool
False)

{- | Return a Quantity's dimensional-exponent witness, without changing its SI value or
absolute-temperature tag.
-}
quantityDimension :: Quantity -> Dimension
quantityDimension :: Quantity -> Dimension
quantityDimension (Quantity Dimension
d Rational
_ Bool
_) = Dimension
d

{- | Return exact Rational SI value of a Quantity. For absolute temperature this is an
absolute kelvin value; preserve quantityDimension when interpreting the number.
-}
siValue :: Quantity -> Rational
siValue :: Quantity -> Rational
siValue (Quantity Dimension
_ Rational
x Bool
_) = Rational
x

{- | Given destination Unit then Quantity, convert its SI value to that unit. Reject
incompatible dimensions. Apply affine offsets only for absolute temperatures, not
differences.
-}
inUnit :: Unit -> Quantity -> Either MathError Rational
inUnit :: Unit -> Quantity -> Either MathError Rational
inUnit Unit
u (Quantity Dimension
d Rational
x Bool
absoluteTemp)
    | Unit -> Dimension
unitDimension Unit
u Dimension -> Dimension -> Bool
forall a. Eq a => a -> a -> Bool
/= Dimension
d = MathError -> Either MathError Rational
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"incompatible dimensions")
    | Bool
otherwise = Rational -> Either MathError Rational
forall a b. b -> Either a b
Right ((Rational
x Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
- (if Bool
absoluteTemp then Unit -> Rational
unitOffset Unit
u else Rational
0)) Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/ Unit -> Rational
unitScale Unit
u)

{- | Given source Unit, destination Unit, then Rational value, convert exactly through SI.
Incompatible dimensions fail. Example: convert kilometre metre 2 == Right 2000.
-}
convert :: Unit -> Unit -> Rational -> Either MathError Rational
convert :: Unit -> Unit -> Rational -> Either MathError Rational
convert Unit
a Unit
b Rational
x = Unit -> Quantity -> Either MathError Rational
inUnit Unit
b (Unit -> Rational -> Quantity
quantity Unit
a Rational
x)

addQuantity
    , subtractQuantity
    , multiplyQuantity
    , divideQuantity ::
        Quantity -> Quantity -> Either MathError Quantity

{- | Add equal-dimension quantities. Two absolute temperatures cannot be added; absolute
plus difference produces an absolute temperature. Incompatible dimensions return Left
DomainError.
-}
addQuantity :: Quantity -> Quantity -> Either MathError Quantity
addQuantity (Quantity Dimension
d Rational
a Bool
absA) (Quantity Dimension
e Rational
b Bool
absB)
    | Dimension
d Dimension -> Dimension -> Bool
forall a. Eq a => a -> a -> Bool
/= Dimension
e = MathError -> Either MathError Quantity
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"incompatible dimensions")
    | Bool
absA Bool -> Bool -> Bool
&& Bool
absB = MathError -> Either MathError Quantity
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"cannot add absolute temperatures")
    | Bool
otherwise = Quantity -> Either MathError Quantity
forall a b. b -> Either a b
Right (Dimension -> Rational -> Bool -> Quantity
Quantity Dimension
d (Rational
a Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
+ Rational
b) (Bool
absA Bool -> Bool -> Bool
|| Bool
absB))

{- | Subtract equal-dimension quantities. Absolute minus absolute produces a difference;
difference minus absolute is invalid. Incompatible dimensions return Left DomainError.
-}
subtractQuantity :: Quantity -> Quantity -> Either MathError Quantity
subtractQuantity (Quantity Dimension
d Rational
a Bool
absA) (Quantity Dimension
e Rational
b Bool
absB)
    | Dimension
d Dimension -> Dimension -> Bool
forall a. Eq a => a -> a -> Bool
/= Dimension
e = MathError -> Either MathError Quantity
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"incompatible dimensions")
    | Bool -> Bool
not Bool
absA Bool -> Bool -> Bool
&& Bool
absB = MathError -> Either MathError Quantity
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"cannot subtract absolute temperature from difference")
    | Bool
otherwise = Quantity -> Either MathError Quantity
forall a b. b -> Either a b
Right (Dimension -> Rational -> Bool -> Quantity
Quantity Dimension
d (Rational
a Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
- Rational
b) (Bool
absA Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
absB))

{- | Multiply SI values and add dimension exponents. Any absolute-temperature operand fails
with DomainError; temperature differences can participate in products.
-}
multiplyQuantity :: Quantity -> Quantity -> Either MathError Quantity
multiplyQuantity (Quantity Dimension
d Rational
a Bool
absA) (Quantity Dimension
e Rational
b Bool
absB)
    | Bool
absA Bool -> Bool -> Bool
|| Bool
absB = MathError -> Either MathError Quantity
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"cannot multiply absolute temperatures")
    | Bool
otherwise = Quantity -> Either MathError Quantity
forall a b. b -> Either a b
Right (Dimension -> Rational -> Bool -> Quantity
Quantity ((Integer -> Integer -> Integer)
-> Dimension -> Dimension -> Dimension
combine Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
(+) Dimension
d Dimension
e) (Rational
a Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
* Rational
b) Bool
False)

{- | Divide SI values and subtract dimension exponents. Reject a zero divisor or
absolute-temperature operands. Result preserves exact Rational arithmetic.
-}
divideQuantity :: Quantity -> Quantity -> Either MathError Quantity
divideQuantity (Quantity Dimension
d Rational
a Bool
absA) (Quantity Dimension
e Rational
b Bool
absB)
    | Bool
absA Bool -> Bool -> Bool
|| Bool
absB = MathError -> Either MathError Quantity
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"cannot divide absolute temperatures")
    | Rational
b Rational -> Rational -> Bool
forall a. Eq a => a -> a -> Bool
== Rational
0 = MathError -> Either MathError Quantity
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"quantity division by zero")
    | Bool
otherwise = Quantity -> Either MathError Quantity
forall a b. b -> Either a b
Right (Dimension -> Rational -> Bool -> Quantity
Quantity ((Integer -> Integer -> Integer)
-> Dimension -> Dimension -> Dimension
combine (-) Dimension
d Dimension
e) (Rational
a Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/ Rational
b) Bool
False)

-- | SI prefix name,symbol,and power-of-ten exponent. prefixed rejects affine units.
data SIPrefix = SIPrefix
    { SIPrefix -> String
prefixName :: String
    -- ^ SI prefix name, such as kilo.
    , SIPrefix -> String
prefixSymbol :: String
    -- ^ SI prefix symbol, such as k; micro uses ASCII u in this catalog.
    , SIPrefix -> Int
prefixExponent :: Int
    -- ^ Integer power of ten multiplying the base unit scale.
    }
    deriving (SIPrefix -> SIPrefix -> Bool
(SIPrefix -> SIPrefix -> Bool)
-> (SIPrefix -> SIPrefix -> Bool) -> Eq SIPrefix
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: SIPrefix -> SIPrefix -> Bool
== :: SIPrefix -> SIPrefix -> Bool
$c/= :: SIPrefix -> SIPrefix -> Bool
/= :: SIPrefix -> SIPrefix -> Bool
Eq, Int -> SIPrefix -> ShowS
[SIPrefix] -> ShowS
SIPrefix -> String
(Int -> SIPrefix -> ShowS)
-> (SIPrefix -> String) -> ([SIPrefix] -> ShowS) -> Show SIPrefix
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> SIPrefix -> ShowS
showsPrec :: Int -> SIPrefix -> ShowS
$cshow :: SIPrefix -> String
show :: SIPrefix -> String
$cshowList :: [SIPrefix] -> ShowS
showList :: [SIPrefix] -> ShowS
Show)

{- | BIPM prefixes through the 2022 quetta/ronna and ronto/quecto additions.
Source: https://www.bipm.org/en/measurement-units/si-prefixes
-}
siPrefixes :: [SIPrefix]
siPrefixes :: [SIPrefix]
siPrefixes =
    [ String -> String -> Int -> SIPrefix
SIPrefix String
n String
s Int
e
    | (String
n, String
s, Int
e) <-
        [ (String
"quetta", String
"Q", Int
30)
        , (String
"ronna", String
"R", Int
27)
        , (String
"yotta", String
"Y", Int
24)
        , (String
"zetta", String
"Z", Int
21)
        , (String
"exa", String
"E", Int
18)
        , (String
"peta", String
"P", Int
15)
        , (String
"tera", String
"T", Int
12)
        , (String
"giga", String
"G", Int
9)
        , (String
"mega", String
"M", Int
6)
        , (String
"kilo", String
"k", Int
3)
        , (String
"hecto", String
"h", Int
2)
        , (String
"deca", String
"da", Int
1)
        , (String
"deci", String
"d", -Int
1)
        , (String
"centi", String
"c", -Int
2)
        , (String
"milli", String
"m", -Int
3)
        , (String
"micro", String
"u", -Int
6)
        , (String
"nano", String
"n", -Int
9)
        , (String
"pico", String
"p", -Int
12)
        , (String
"femto", String
"f", -Int
15)
        , (String
"atto", String
"a", -Int
18)
        , (String
"zepto", String
"z", -Int
21)
        , (String
"yocto", String
"y", -Int
24)
        , (String
"ronto", String
"r", -Int
27)
        , (String
"quecto", String
"q", -Int
30)
        ]
    ]

{- | Given SIPrefix then zero-offset Unit, multiply its scale by 10^prefixExponent and
prepend its symbol. Reject affine units or absolute exponent above 10000.
-}
prefixed :: SIPrefix -> Unit -> Either MathError Unit
prefixed :: SIPrefix -> Unit -> Either MathError Unit
prefixed SIPrefix
p Unit
u
    | Unit -> Rational
unitOffset Unit
u Rational -> Rational -> Bool
forall a. Eq a => a -> a -> Bool
/= Rational
0 = MathError -> Either MathError Unit
forall a b. a -> Either a b
Left (String -> MathError
DomainError String
"cannot prefix affine unit")
    | Integer -> Integer
forall a. Num a => a -> a
abs (Int -> Integer
forall a. Integral a => a -> Integer
toInteger (SIPrefix -> Int
prefixExponent SIPrefix
p)) Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
10000 =
        MathError -> Either MathError Unit
forall a b. a -> Either a b
Left (String -> MathError
InvalidArgument String
"prefix exponent exceeds resource limit")
    | Bool
otherwise =
        let e :: Int
e = SIPrefix -> Int
prefixExponent SIPrefix
p; s :: Rational
s = if Int
e Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0 then Integer -> Rational
forall a. Num a => Integer -> a
fromInteger (Integer
10 Integer -> Int -> Integer
forall a b. (Num a, Integral b) => a -> b -> a
^ Int
e) else Rational
1 Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/ Integer -> Rational
forall a. Num a => Integer -> a
fromInteger (Integer
10 Integer -> Int -> Integer
forall a b. (Num a, Integral b) => a -> b -> a
^ Int -> Int
forall a. Num a => a -> a
negate Int
e)
         in Unit -> Either MathError Unit
forall a b. b -> Either a b
Right (String -> Dimension -> Rational -> Rational -> Unit
Unit (SIPrefix -> String
prefixSymbol SIPrefix
p String -> ShowS
forall a. [a] -> [a] -> [a]
++ Unit -> String
unitName Unit
u) (Unit -> Dimension
unitDimension Unit
u) (Rational
s Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
* Unit -> Rational
unitScale Unit
u) Rational
0)

metre, kilometre, centimetre, millimetre, inch, foot, yard, mile, nauticalMile :: Unit

{- | The metre Unit: 1 metre. Non-temperature definitions use an exact Rational scale and
zero affine offset. Use quantity or convert to construct/convert values; temperature
differences use temperatureDifference.
-}
metre :: Unit
metre = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"m" Dimension
lengthDimension Rational
1 Rational
0

{- | The kilometre Unit: 1000 metres. Non-temperature definitions use an exact Rational
scale and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
kilometre :: Unit
kilometre = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"km" Dimension
lengthDimension Rational
1000 Rational
0

{- | The centimetre Unit: 0.01 metre. Non-temperature definitions use an exact Rational
scale and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
centimetre :: Unit
centimetre = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"cm" Dimension
lengthDimension Rational
0.01 Rational
0

{- | The millimetre Unit: 0.001 metre. Non-temperature definitions use an exact Rational
scale and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
millimetre :: Unit
millimetre = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"mm" Dimension
lengthDimension Rational
0.001 Rational
0

{- | The inch Unit: 0.0254 metre. Non-temperature definitions use an exact Rational scale
and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
inch :: Unit
inch = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"in" Dimension
lengthDimension Rational
0.0254 Rational
0

{- | The foot Unit: 0.3048 metre. Non-temperature definitions use an exact Rational scale
and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
foot :: Unit
foot = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"ft" Dimension
lengthDimension Rational
0.3048 Rational
0

{- | The yard Unit: 0.9144 metre. Non-temperature definitions use an exact Rational scale
and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
yard :: Unit
yard = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"yd" Dimension
lengthDimension Rational
0.9144 Rational
0

{- | The mile Unit: 1609.344 metres. Non-temperature definitions use an exact Rational scale
and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
mile :: Unit
mile = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"mi" Dimension
lengthDimension Rational
1609.344 Rational
0

{- | The nauticalMile Unit: 1852 metres. Non-temperature definitions use an exact Rational
scale and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
nauticalMile :: Unit
nauticalMile = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"nmi" Dimension
lengthDimension Rational
1852 Rational
0

squareMetre, hectare, cubicMetre, litre, millilitre :: Unit

{- | The squareMetre Unit: 1 square metre. Non-temperature definitions use an exact Rational
scale and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
squareMetre :: Unit
squareMetre = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"m^2" (Integer -> Dimension -> Dimension
scaleDimension Integer
2 Dimension
lengthDimension) Rational
1 Rational
0

{- | The hectare Unit: 10000 square metres. Non-temperature definitions use an exact
Rational scale and zero affine offset. Use quantity or convert to construct/convert
values; temperature differences use temperatureDifference.
-}
hectare :: Unit
hectare = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"ha" (Integer -> Dimension -> Dimension
scaleDimension Integer
2 Dimension
lengthDimension) Rational
10000 Rational
0

{- | The cubicMetre Unit: 1 cubic metre. Non-temperature definitions use an exact Rational
scale and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
cubicMetre :: Unit
cubicMetre = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"m^3" (Integer -> Dimension -> Dimension
scaleDimension Integer
3 Dimension
lengthDimension) Rational
1 Rational
0

{- | The litre Unit: 0.001 cubic metre. Non-temperature definitions use an exact Rational
scale and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
litre :: Unit
litre = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"L" (Integer -> Dimension -> Dimension
scaleDimension Integer
3 Dimension
lengthDimension) Rational
0.001 Rational
0

{- | The millilitre Unit: 0.000001 cubic metre. Non-temperature definitions use an exact
Rational scale and zero affine offset. Use quantity or convert to construct/convert
values; temperature differences use temperatureDifference.
-}
millilitre :: Unit
millilitre = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"mL" (Integer -> Dimension -> Dimension
scaleDimension Integer
3 Dimension
lengthDimension) Rational
0.000001 Rational
0

kilogram, gram, tonne, pound, second, minute, hour, day :: Unit

{- | The kilogram Unit: 1 kilogram. Non-temperature definitions use an exact Rational scale
and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
kilogram :: Unit
kilogram = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"kg" Dimension
massDimension Rational
1 Rational
0

{- | The gram Unit: 0.001 kilogram. Non-temperature definitions use an exact Rational scale
and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
gram :: Unit
gram = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"g" Dimension
massDimension Rational
0.001 Rational
0

{- | The tonne Unit: 1000 kilograms. Non-temperature definitions use an exact Rational scale
and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
tonne :: Unit
tonne = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"t" Dimension
massDimension Rational
1000 Rational
0

{- | The pound Unit: 0.45359237 kilogram. Non-temperature definitions use an exact Rational
scale and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
pound :: Unit
pound = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"lb" Dimension
massDimension Rational
0.45359237 Rational
0

{- | The second Unit: 1 second. Non-temperature definitions use an exact Rational scale and
zero affine offset. Use quantity or convert to construct/convert values; temperature
differences use temperatureDifference.
-}
second :: Unit
second = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"s" Dimension
timeDimension Rational
1 Rational
0

{- | The minute Unit: 60 seconds. Non-temperature definitions use an exact Rational scale
and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
minute :: Unit
minute = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"min" Dimension
timeDimension Rational
60 Rational
0

{- | The hour Unit: 3600 seconds. Non-temperature definitions use an exact Rational scale
and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
hour :: Unit
hour = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"h" Dimension
timeDimension Rational
3600 Rational
0

{- | The day Unit: 86400 seconds. Non-temperature definitions use an exact Rational scale
and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
day :: Unit
day = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"d" Dimension
timeDimension Rational
86400 Rational
0

metresPerSecond, kilometresPerHour, milesPerHour, metresPerSecondSquared :: Unit

{- | The metresPerSecond Unit: 1 metre per second. Non-temperature definitions use an exact
Rational scale and zero affine offset. Use quantity or convert to construct/convert
values; temperature differences use temperatureDifference.
-}
metresPerSecond :: Unit
metresPerSecond = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"m/s" ((Integer -> Integer -> Integer)
-> Dimension -> Dimension -> Dimension
combine (-) Dimension
lengthDimension Dimension
timeDimension) Rational
1 Rational
0

{- | The kilometresPerHour Unit: 1000/3600 metres per second. Non-temperature definitions
use an exact Rational scale and zero affine offset. Use quantity or convert to
construct/convert values; temperature differences use temperatureDifference.
-}
kilometresPerHour :: Unit
kilometresPerHour = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"km/h" (Unit -> Dimension
unitDimension Unit
metresPerSecond) (Rational
1000 Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/ Rational
3600) Rational
0

{- | The milesPerHour Unit: 1609.344/3600 metres per second. Non-temperature definitions use
an exact Rational scale and zero affine offset. Use quantity or convert to
construct/convert values; temperature differences use temperatureDifference.
-}
milesPerHour :: Unit
milesPerHour = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"mi/h" (Unit -> Dimension
unitDimension Unit
metresPerSecond) (Rational
1609.344 Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/ Rational
3600) Rational
0

{- | The metresPerSecondSquared Unit: 1 metre per second squared. Non-temperature
definitions use an exact Rational scale and zero affine offset. Use quantity or convert
to construct/convert values; temperature differences use temperatureDifference.
-}
metresPerSecondSquared :: Unit
metresPerSecondSquared = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"m/s^2" ((Integer -> Integer -> Integer)
-> Dimension -> Dimension -> Dimension
combine (-) Dimension
lengthDimension (Integer -> Dimension -> Dimension
scaleDimension Integer
2 Dimension
timeDimension)) Rational
1 Rational
0

newton, joule, watt, pascal, bar, atmosphere :: Unit

{- | The newton Unit: 1 kg*m/s^2. Non-temperature definitions use an exact Rational scale
and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
newton :: Unit
newton = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"N" ((Integer -> Integer -> Integer)
-> Dimension -> Dimension -> Dimension
combine Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
(+) Dimension
massDimension (Unit -> Dimension
unitDimension Unit
metresPerSecondSquared)) Rational
1 Rational
0

{- | The joule Unit: 1 newton metre. Non-temperature definitions use an exact Rational scale
and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
joule :: Unit
joule = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"J" ((Integer -> Integer -> Integer)
-> Dimension -> Dimension -> Dimension
combine Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
(+) (Unit -> Dimension
unitDimension Unit
newton) Dimension
lengthDimension) Rational
1 Rational
0

{- | The watt Unit: 1 joule per second. Non-temperature definitions use an exact Rational
scale and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
watt :: Unit
watt = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"W" ((Integer -> Integer -> Integer)
-> Dimension -> Dimension -> Dimension
combine (-) (Unit -> Dimension
unitDimension Unit
joule) Dimension
timeDimension) Rational
1 Rational
0

{- | The pascal Unit: 1 newton per square metre. Non-temperature definitions use an exact
Rational scale and zero affine offset. Use quantity or convert to construct/convert
values; temperature differences use temperatureDifference.
-}
pascal :: Unit
pascal = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"Pa" ((Integer -> Integer -> Integer)
-> Dimension -> Dimension -> Dimension
combine (-) (Unit -> Dimension
unitDimension Unit
newton) (Unit -> Dimension
unitDimension Unit
squareMetre)) Rational
1 Rational
0

{- | The bar Unit: 100000 pascals. Non-temperature definitions use an exact Rational scale
and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
bar :: Unit
bar = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"bar" (Unit -> Dimension
unitDimension Unit
pascal) Rational
100000 Rational
0

{- | The atmosphere Unit: 101325 pascals. Non-temperature definitions use an exact Rational
scale and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
atmosphere :: Unit
atmosphere = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"atm" (Unit -> Dimension
unitDimension Unit
pascal) Rational
101325 Rational
0

kelvin, celsius, fahrenheit, hertz, bit, byte, kibibyte, mebibyte, gigabyte :: Unit

{- | The kelvin Unit: absolute temperature with SI=value. Non-temperature definitions use an
exact Rational scale and zero affine offset. Use quantity or convert to
construct/convert values; temperature differences use temperatureDifference.
-}
kelvin :: Unit
kelvin = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"K" Dimension
temperatureDimension Rational
1 Rational
0

{- | The celsius Unit: absolute temperature with SI=value+273.15 kelvin. Non-temperature
definitions use an exact Rational scale and zero affine offset. Use quantity or convert
to construct/convert values; temperature differences use temperatureDifference.
-}
celsius :: Unit
celsius = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"degC" Dimension
temperatureDimension Rational
1 Rational
273.15

{- | The fahrenheit Unit: absolute temperature with SI=(value-32)*5/9+273.15 kelvin.
Non-temperature definitions use an exact Rational scale and zero affine offset. Use
quantity or convert to construct/convert values; temperature differences use
temperatureDifference.
-}
fahrenheit :: Unit
fahrenheit = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"degF" Dimension
temperatureDimension (Rational
5 Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/ Rational
9) (Rational
273.15 Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
- Rational
32 Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
* Rational
5 Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/ Rational
9)

{- | The hertz Unit: 1 inverse second. Non-temperature definitions use an exact Rational
scale and zero affine offset. Use quantity or convert to construct/convert values;
temperature differences use temperatureDifference.
-}
hertz :: Unit
hertz = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"Hz" (Integer -> Dimension -> Dimension
scaleDimension (-Integer
1) Dimension
timeDimension) Rational
1 Rational
0

{- | The bit Unit: 1 data bit. Non-temperature definitions use an exact Rational scale and
zero affine offset. Use quantity or convert to construct/convert values; temperature
differences use temperatureDifference.
-}
bit :: Unit
bit = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"bit" Dimension
dataDimension Rational
1 Rational
0

{- | The byte Unit: 8 data bits. Non-temperature definitions use an exact Rational scale and
zero affine offset. Use quantity or convert to construct/convert values; temperature
differences use temperatureDifference.
-}
byte :: Unit
byte = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"B" Dimension
dataDimension Rational
8 Rational
0

{- | The kibibyte Unit: 8192 data bits (1024 bytes). Non-temperature definitions use an
exact Rational scale and zero affine offset. Use quantity or convert to
construct/convert values; temperature differences use temperatureDifference.
-}
kibibyte :: Unit
kibibyte = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"KiB" Dimension
dataDimension (Rational
8 Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
* Rational
1024) Rational
0

{- | The mebibyte Unit: 8388608 data bits (1024^2 bytes). Non-temperature definitions use an
exact Rational scale and zero affine offset. Use quantity or convert to
construct/convert values; temperature differences use temperatureDifference.
-}
mebibyte :: Unit
mebibyte = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"MiB" Dimension
dataDimension (Rational
8 Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
* Rational
1024 Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
* Rational
1024) Rational
0

{- | The gigabyte Unit: 8000000000 data bits (10^9 bytes). Non-temperature definitions use
an exact Rational scale and zero affine offset. Use quantity or convert to
construct/convert values; temperature differences use temperatureDifference.
-}
gigabyte :: Unit
gigabyte = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"GB" Dimension
dataDimension (Rational
8 Rational -> Rational -> Rational
forall a. Num a => a -> a -> a
* Rational
10 Rational -> Int -> Rational
forall a b. (Num a, Integral b) => a -> b -> a
^ (Int
9 :: Int)) Rational
0

radian, degree, gradian :: Unit

{- | The radian Unit: 1 radian. Non-temperature definitions use an exact Rational scale and
zero affine offset. Use quantity or convert to construct/convert values; temperature
differences use temperatureDifference.
-}
radian :: Unit
radian = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"rad" Dimension
angleDimension Rational
1 Rational
0

{- | The degree Unit: pi/180 radian, using the stored 50-place Rational pi approximation.
Non-temperature definitions use an exact Rational scale and zero affine offset. Use
quantity or convert to construct/convert values; temperature differences use
temperatureDifference.
-}
degree :: Unit
degree = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"deg" Dimension
angleDimension (Rational
piR Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/ Rational
180) Rational
0

{- | The gradian Unit: pi/200 radian, using the stored Rational pi approximation.
Non-temperature definitions use an exact Rational scale and zero affine offset. Use
quantity or convert to construct/convert values; temperature differences use
temperatureDifference.
-}
gradian :: Unit
gradian = String -> Dimension -> Rational -> Rational -> Unit
Unit String
"grad" Dimension
angleDimension (Rational
piR Rational -> Rational -> Rational
forall a. Fractional a => a -> a -> a
/ Rational
200) Rational
0

piR :: Rational
piR :: Rational
piR = case (Constant -> Bool) -> [Constant] -> [Constant]
forall a. (a -> Bool) -> [a] -> [a]
filter ((String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"pi") (String -> Bool) -> (Constant -> String) -> Constant -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Constant -> String
constantName) [Constant]
mathematicalConstants of
    Constant
c : [Constant]
_ -> Constant -> Rational
constantValue Constant
c
    [] -> Rational
3.14159265358979323846264338327950288419716939937510
radiansToDegrees, degreesToRadians, radiansToGradians, gradiansToRadians :: (Floating a) => a -> a

{- | Convert radians to degrees by multiplying by 180/pi in the chosen Floating type. No
finite-input validation is added.
-}
radiansToDegrees :: forall a. Floating a => a -> a
radiansToDegrees a
x = a
x a -> a -> a
forall a. Num a => a -> a -> a
* a
180 a -> a -> a
forall a. Fractional a => a -> a -> a
/ a
forall a. Floating a => a
pi

-- | Convert degrees to radians by multiplying by pi/180 in the chosen Floating type.
degreesToRadians :: forall a. Floating a => a -> a
degreesToRadians a
x = a
x a -> a -> a
forall a. Num a => a -> a -> a
* a
forall a. Floating a => a
pi a -> a -> a
forall a. Fractional a => a -> a -> a
/ a
180

-- | Convert radians to gradians (400 per full turn) by multiplying by 200/pi.
radiansToGradians :: forall a. Floating a => a -> a
radiansToGradians a
x = a
x a -> a -> a
forall a. Num a => a -> a -> a
* a
200 a -> a -> a
forall a. Fractional a => a -> a -> a
/ a
forall a. Floating a => a
pi

-- | Convert gradians to radians by multiplying by pi/200.
gradiansToRadians :: forall a. Floating a => a -> a
gradiansToRadians a
x = a
x a -> a -> a
forall a. Num a => a -> a -> a
* a
forall a. Floating a => a
pi a -> a -> a
forall a. Fractional a => a -> a -> a
/ a
200