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 ne \$expected} {puts "\$cmd -> \$res, expected \$expected"} } #-- The test suite should silently pass when this file is [source]d: ? {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] ---- [Arts and crafts of Tcl-Tk programming] [Category Mathematics] }