I'd remove floating-point numbers from the picture. You're almost always going to have problems with primitives, but especially when dealing with the weird details of the IEEE 754 type.
Instead, I'd represent probabilities using a ratio type:
record Probability : Type where
MkProbability : (numerator : Nat) ->
(denominator : Nat) ->
LTE numerator (S denominator) ->
Probability
LTE
is a type where values only exist when the first Nat
is less than or equal to the second Nat
. The (S denominator)
is to ensure we don't have a denominator of zero. This means MkProbability 2 1 (LTESucc LTEZero)
is valid and represents a probability 1.0
, looks weird but ensures validity.
We can then get a Float
out of the type:
toFloat : Probability -> Float
toFloat (MkProbability n d _) =
fromInteger (toIntegerNat n) / fromInteger (toIntegerNat (S d))
Another benefit is that this is arbitrary precision until we convert to a Float
.
A problem is that you're probably going to have to build large LTE
values. Using isLTE
for runtime values will probably help here!