How to Prepare Rational Numbers to Handle Fractions in Standard ML
Masaki Haga
Posted on March 26, 2020
Let's implement a data type like Data.Ratio
of Haskell
in Standard ML that will handle rational numbers (with greater precision than floating point).
signature
signature RATIO =
sig
type ratio = int * int
exception DivideByZero
val fromIntPair : int * int -> ratio
val + : ratio * ratio -> ratio
val - : ratio * ratio -> ratio
val * : ratio * ratio -> ratio
val / : ratio * ratio -> ratio
val > : ratio * ratio -> bool
val < : ratio * ratio -> bool
end
The name of data type is ratio
. It is actually a couple of integer.
Prepare conversion functions fromIntPair
, addition, subtraction, multiplication and division of fractions, and comparison operations.
In practice this is a couple of integer, so use =
for the equal sign.
structure
structure Ratio : RATIO =
struct
type ratio = int * int
exception DivideByZero
fun fromIntPair (num, 0) = raise DivideByZero
| fromIntPair (0, den) = (0, 1)
| fromIntPair (num, den) =
let fun gcd (x, y) = if x = y then x
else if x > y then gcd (x - y, y)
else gcd (x, y - x)
val g = if den > 0 then gcd (abs num, abs den)
else ~(gcd (abs num, abs den))
in (num div g, den div g)
end
fun (x, y) + (z, w) = fromIntPair (Int.+(Int.*(w, x), Int.*(y, z)), Int.*(y, w))
fun (x, y) - (z, w) = fromIntPair (Int.-(Int.*(w, x), Int.*(y, z)), Int.*(y, w))
fun (x, y) * (z, w) = fromIntPair (Int.*(x, z), Int.*(y, w))
fun (x, y) / (z, w) = fromIntPair (Int.*(x, w), Int.*(y, z))
fun (x, y) > (z, w) = Int.>(Int.*(w, x), Int.*(y, z))
fun (x, y) < (z, w) = Int.<(Int.*(w, x), Int.*(y, z))
end
The argument to fromIntPair
will call an exception if the denominator is 0, and return (0, 1)
if the numerator is 0.
Otherwise, the greatest common divisor is obtained by taking the absolute value and dividing by the greatest common divisor obtained by the numerator and the denominator.
If the denominator is negative, the sign is reversed because only the numerator is signed.
In the four arithmetic operations of fractions, the numerator is calculated with the denominator aligned and then reduced with fromIntPair
.
In comparison operations, the numerators are compared after the denominators are aligned.
example
Use open
to expand a structure, and use local
to avoid hiding the constraints of the original four operations and comparisons.
- local open Ratio
= in
= val twoThird =
= let val oneThird = fromIntPair (1, 3)
= in oneThird + oneThird
= end
= end
= ;
val twoThird = (2,3) : int * int
Posted on March 26, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.