DEV Community

Masaki Haga
Masaki Haga

Posted on • Updated on

How to Prepare Rational Numbers to Handle Fractions in Standard ML

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Discussion (0)