if 0 {Richard Suchenwirth 2005-03-22 - Rational numbers, a.k.a. fractions (see Fraction math), can be thought of as pairs of integers {numerator denominator}, such that their "real" numerical value is *numerator/denominator* (and not in integer nor "double" division!). They can be more precise than any "float" or "double" numbers on computers, as those can't exactly represent any fractions whose denominator isn't a power of 2 - consider 1/3 which can not at any precision be exactly represented as floating-point number to base 2, nor as decimal fraction (base 10), even if bignum. Reading in SICP once more, I wanted to play with rationals in Tcl again - so here's another evening fun project.

An obvious string representation of a rational is of course "n/d". The following "constructor" does that, plus it normalizes the signs, reduces to lowest terms, and returns just the integer n if d==1:}

proc rat {n d} { if {!$d} {error "denominator can't be 0"} if {$d<0} {set n [- $n]; set d [- $d]} set g [gcd $n $d] set n [/ $n $g] set d [/ $d $g] expr {$d==1? $n: "$n/$d" } }

if 0 {Conversely, this "deconstructor" splits zero or more rational or integer strings into num and den variables, such that [ratsplit 1/3 a b] assigns 1 to a and 3 to b:}

proc ratsplit args { foreach {r _n _d} $args { upvar 1 $_n n $_d d foreach {n d} [split $r /] break if {$d eq ""} {set d 1} } }

#-- Four-species math on "rats":

proc rat+ {r s} { ratsplit $r a b $s c d rat [+ [* $a $d] [* $c $b]] [* $b $d] } proc rat- {r s} { ratsplit $r a b $s c d rat [- [* $a $d] [* $c $b]] [* $b $d] } proc rat* {r s} { ratsplit $r a b $s c d rat [* $a $c] [* $b $d] } proc rat/ {r s} { ratsplit $r a b $s c d rat [* $a $d] [* $b $c] }

if 0 { Arithmetical helper functions can be wrapped with func if they only consist of one call of expr:}

proc func {name argl body} {proc $name $argl [list expr $body]}

#-- Greatest common denominator:

func gcd {u v} {$u? [gcd [% $v $u] $u]: abs($v)}

#-- Binary expr operators exported:

foreach op {+ * / %} {func $op {a b} \$a$op\$b}

#-- "-" can have 1 or 2 operands:

func - {a {b ""}} {$b eq ""? -$a: $a-$b}

#-- a little tester reports the unexpected:

proc ? {cmd expected} { catch {uplevel 1 $cmd} res if {$res != $expected} {puts "$cmd -> $res, expected $expected"} }

#-- The test suite should silently pass when this file is sourced:

? {rat 42 6} 7 ? {rat 1 -2} -1/2 ? {rat -1 -2} 1/2 ? {rat 1 0} "denominator can't be 0" ? {rat+ 1/3 1/3} 2/3 ? {rat+ 1/2 1/2} 1 ? {rat+ 1/2 1/3} 5/6 ? {rat+ 1 1/2} 3/2 ? {rat- 1/2 1/8} 3/8 ? {rat- 1/2 1/-8} 5/8 ? {rat- 1/7 1/7} 0 ? {rat* 1/2 1/2} 1/4 ? {rat/ 1/4 1/4} 1 ? {rat/ 4 -6} -2/3

if 0 { See also Fraction Math