Generalised floating point addition: Difference between revisions

→‎Tcl: Added implementation
(Rewrote the task to be less prescriptive)
(→‎Tcl: Added implementation)
Line 922:
p 12345679012345679012345679012345679e36 * 81 + 1e36</lang>
All result in 1.0e+72.
 
=={{header|Tcl}}==
Tcl does not allow overriding the default mathematical operators (it does allow you to define your own expression engine — this is even relatively simple to do — but this is out of the scope of this task) but it also allows expressions to be written using a Lisp-like prefix form:
<lang tcl>namespace path ::tcl::mathop
for {set n -7; set e 63} {$n <= 21} {incr n;incr e -9} {
append m 012345679
puts $n:[+ [* [format "%se%s" $m $e] 81] 1e${e}]
}</lang>
The above won't work as intended though; the default implementation of the mathematical operators does not handle arbitrary precision floats (though in fact it will produce the right output with these specific values; this is a feature of the rounding used). So here is a version that does everything properly:
<lang tcl>namespace eval longfloat {
proc + {num args} {
set num [impl::Tidy $num]
foreach x $args {
set num [impl::Add $num [impl::Tidy $x]]
}
return [impl::Normalize $num]
}
proc * {num args} {
set num [impl::Tidy $num]
foreach x $args {
set num [impl::Mul $num [impl::Tidy $x]]
}
return [impl::Normalize $num]
}
 
namespace export + *
 
# This namespace contains the implementations of the operations and
# isn't intended to be called directly by the rest of the program.
namespace eval impl {
variable FloatRE \
{(?i)^([-+]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+))(?:e([-+]?[0-9]+))?$}
 
proc Tidy {n} {
variable FloatRE
# Parse the input
if {[llength $n] == 2} {
return $n
} elseif {[llength $n] != 1} {
return -level 2 -code error "non-numeric argument"
} elseif {![regexp $FloatRE $n -> mantissa exponent]} {
return -level 2 -code error "non-numeric argument"
}
 
# Default exponent is zero
if {$exponent eq ""} {
set exponent 0
}
# Eliminate the decimal point
set bits [split $mantissa .]
if {[llength $bits] == 2} {
incr exponent [expr {-[string length [lindex $bits 1]]}]
set mantissa [join $bits ""]
}
# Trim useless leading zeroes
return [list [regsub {^([-+]?)0*([0-9])} $mantissa {\1\2}] $exponent]
}
 
proc Normalize {n} {
lassign $n mantissa exponent
# Trim useless trailing zeroes
while {[regexp {^([-+]?.+?)(0+)$} $mantissa -> head tail]} {
set mantissa $head
incr exponent [string length $tail]
}
# Human-readable form, please
return ${mantissa}e${exponent}
}
 
# Addition and multiplication on pairs of arbitrary-precision floats,
# in decomposed form
proc Add {a b} {
lassign $a am ae
lassign $b bm be
set de [expr {$ae - $be}]
if {$de < 0} {
append bm [string repeat 0 [expr {-$de}]]
} elseif {$de > 0} {
incr ae [expr {-$de}]
append am [string repeat 0 $de]
}
list [expr {$am+$bm}] $ae
}
proc Mul {a b} {
lassign $a am ae
lassign $b bm be
list [expr {$am * $bm}] [expr {$ae + $be}]
}
}
}</lang>
Now we can demonstrate (compare with the original code to see how little has changed syntactically):
<lang tcl>namespace path longfloat
for {set n -7; set e 63} {$n <= 21} {incr n;incr e -9} {
append m 012345679
puts $n:[+ [* [format "%se%s" $m $e] 81] 1e${e}]
}</lang>
{{out|Output (trimmed)}}
<pre>
-7:1e72
-6:1e72
-5:1e72
-4:1e72
………
18:1e72
19:1e72
20:1e72
21:1e72
</pre>
Obviously, changing the code inside the <code>impl</code> namespace would alter how the calculation was carried out.
Anonymous user