diff options
Diffstat (limited to 'tcllib/modules/math/decimal.tcl')
-rwxr-xr-x | tcllib/modules/math/decimal.tcl | 1741 |
1 files changed, 1741 insertions, 0 deletions
diff --git a/tcllib/modules/math/decimal.tcl b/tcllib/modules/math/decimal.tcl new file mode 100755 index 0000000..6505fed --- /dev/null +++ b/tcllib/modules/math/decimal.tcl @@ -0,0 +1,1741 @@ +package require Tcl 8.5 +package provide math::decimal 1.0.3 +# +# Copyright 2011, 2013 Mark Alston. All rights reserved. +# +# Redistribution and use in source and binary forms, with or +# without modification, are permitted provided that the following +# conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY Mark Alston ``AS IS'' AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL Mark Alston OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# +# decimal.tcl -- +# +# Tcl implementation of a General Decimal Arithmetic as defined +# by the IEEE 754 standard as given on http:://speleotrove.com/decimal +# +# Decimal numbers are defined as a list of sign mantissa exponent +# +# The following operations are current implemented: +# +# fromstr tostr -- for converting to and from decimal numbers. +# +# add subtract divide multiply abs compare -- basic operations +# max min plus minus copynegate copysign is-zero is-signed +# is-NaN is-infinite is-finite +# +# round_half_even round_half_up round_half_down -- rounding methods +# round_down round_up round_floor round_ceiling +# round_05up +# +# By setting the extended variable to 0 you get the behavior of the decimal +# subset arithmetic X3.274 as defined on +# http://speleotrove.com/decimal/dax3274.html#x3274 +# +# This package passes all tests in test suites: +# http://speleotrove.com/decimal/dectest.html +# and http://speleotrove.com/decimal/dectest0.html +# +# with the following exceptions: +# +# This version fails some tests that require setting the max +# or min exponent to force truncation or rounding. +# +# This version fails some tests which require the sign of zero to be set +# correctly during rounding +# +# This version cannot handle sNaN's (Not sure that they are of any use for +# tcl programmers anyway. +# +# If you find errors in this code please let me know at +# mark at beernut dot com +# +# Decimal -- +# Namespace for the decimal arithmetic procedures +# +namespace eval ::math::decimal { + variable precision 20 + variable maxExponent 999 + variable minExponent -998 + variable tinyExponent [expr {$minExponent - ($precision - 1)}] + variable rounding half_up + variable extended 1 + + # Some useful variables to set. + variable zero [list 0 0 0] + variable one [list 0 1 0] + variable ten [list 0 1 1] + variable onehundred [list 0 1 2] + variable minusone [list 1 1 0] + + namespace export tostr fromstr setVariable getVariable\ + add + subtract - divide / multiply * \ + divide-int remainder \ + fma fused-multiply-add \ + plus minus copynegate negate copysign \ + abs compare max min \ + is-zero is-signed is-NaN is-infinite is-finite \ + round_half_even round_half_up round_half_down \ + round_down round_up round_floor round_ceiling round_05up + +} + +# setVariable +# Set the desired variable +# +# Arguments: +# variable setting +# +# Result: +# None +# +proc ::math::decimal::setVariable {variable setting} { + variable rounding + variable precision + variable extended + variable maxExponent + variable minExponent + variable tinyExponent + + switch -nocase -- $variable { + rounding {set rounding $setting} + precision {set precision $setting} + extended {set extended $setting} + maxExponent {set maxExponent $setting} + minExponent { + set minExponent $setting + set tinyExponent [expr {$minExponent - ($precision - 1)}] + } + default {} + } +} + +# setVariable +# Set the desired variable +# +# Arguments: +# variable setting +# +# Result: +# None +# +proc ::math::decimal::getVariable {variable} { + variable rounding + variable precision + variable extended + variable maxExponent + variable minExponent + + switch -- $variable { + rounding {return $rounding} + precision {return $precision} + extended {return $extended} + maxExponent {return $maxExponent} + minExponent {return $minExponent} + default {} + } +} + +# add or + +# Add two numbers +# +# Arguments: +# a First operand +# b Second operand +# +# Result: +# Sum of both (rescaled) +# +proc ::math::decimal::add {a b {rescale 1}} { + return [+ $a $b $rescale] +} + +proc ::math::decimal::+ {a b {rescale 1}} { + variable extended + variable rounding + foreach {sa ma ea} $a {break} + foreach {sb mb eb} $b {break} + + if {!$extended} { + if {$ma == 0 } { + return $b + } + if {$mb == 0 } { + return $a + } + } + + if { $ma eq "NaN" || $mb eq "NaN" } { + return [list 0 "NaN" 0] + } + + if { $ma eq "Inf" || $mb eq "Inf" } { + if { $ma ne "Inf" } { + return $b + } elseif { $mb ne "Inf" } { + return $a + } elseif { $sb != $sa } { + return [list 0 "NaN" 0] + } else { + return $a + } + } + + if { $ea > $eb } { + set ma [expr {$ma * 10 ** ($ea-$eb)}] + set er $eb + } else { + set mb [expr {$mb * 10 ** ($eb-$ea)}] + set er $ea + } + if { $sa == $sb } { + # Both are either postive or negative + # Sign remains the same. + set mr [expr {$ma + $mb}] + set sr $sa + } else { + # one is negative and one is positive. + # Set sign to the same as the larger number + # and subract the smaller from the larger. + if { $ma > $mb } { + set sr $sa + set mr [expr {$ma - $mb}] + } elseif { $mb > $ma } { + set sr $sb + set mr [expr {$mb - $ma}] + } else { + if { $rounding == "floor" } { + set sr 1 + } else { + set sr 0 + } + set mr 0 + } + } + if { $rescale } { + return [Rescale [list $sr $mr $er]] + } else { + return [list $sr $mr $er] + } +} + +# copynegate -- +# Takes one operand and returns a copy with the sign inverted. +# In this implementation it works nearly the same as minus +# but is probably much faster. The main difference is that no +# rescaling is done. +# +# +# Arguments: +# a operand +# +# Result: +# a with sign flipped +# +proc ::math::decimal::negate { a } { + return [copynegate $a] +} + +proc ::math::decimal::copynegate { a } { + lset a 0 [expr {![lindex $a 0]}] + return $a +} + +# copysign -- +# Takes two operands and returns a copy of the first with the +# sign set to the sign of the second. +# +# +# Arguments: +# a operand +# b operand +# +# Result: +# b with a's sign +# +proc ::math::decimal::copysign { a b } { + lset a 0 [lindex $b 0] + return $a +} + +# minus -- +# subtract 0 $a +# +# Note: does not pass all tests on extended mode. +# +# Arguments: +# a operand +# +# Result: +# 0 - $a +# +proc ::math::decimal::minus { a } { + return [- [list 0 0 0] $a] +} + +# plus -- +# add 0 $a +# +# Note: does not pass all tests on extended mode. +# +# Arguments: +# a operand +# +# Result: +# 0 + $a +# +proc ::math::decimal::plus {a} { + return [+ [list 0 0 0] $a] +} + + + +# subtract or - +# Subtract two numbers (or unary minus) +# +# Arguments: +# a First operand +# b Second operand (optional) +# +# Result: +# Sum of both (rescaled) +# +proc ::math::decimal::subtract {a {b {}} {rescale 1}} { + return [- $a $b] +} + +proc ::math::decimal::- {a {b {}} {rescale 1}} { + variable extended + + if {!$extended} { + foreach {sa ma ea} $a {break} + foreach {sb mb eb} $b {break} + if {$ma == 0 } { + lset b 0 [expr {![lindex $b 0]}] + return $b + } + if {$mb == 0 } { + return $a + } + } + + if { $b == {} } { + lset a 0 [expr {![lindex $a 0]}] + return $a + } else { + lset b 0 [expr {![lindex $b 0]}] + return [+ $a $b $rescale] + } +} + + +# compare +# Compare two numbers. +# +# Arguments: +# a First operand +# b Second operand +# +# Result: +# 1 if a is larger than b +# 0 if a is equal to b +# -1 if a is smaller than b. +# +proc ::math::decimal::compare {a b} { + foreach {sa ma ea} $a {break} + foreach {sb mb eb} $b {break} + + if { $sa != $sb } { + if {$ma != 0 } { + set ma 1 + set ea 0 + } elseif { $mb != 0 } { + set mb 1 + set eb 0 + } else { + return 0 + } + } + if { $ma eq "Inf" && $mb eq "Inf" } { + if { $sa == $sb } { + return 0 + } elseif { $sa > $sb } { + return -1 + } else { + return 1 + } + } + + set comparison [- [list $sa $ma $ea] [list $sb $mb $eb] 0] + + if { [lindex $comparison 0] && [lindex $comparison 1] != 0 } { + return -1 + } elseif { [lindex $comparison 1] == 0 } { + return 0 + } else { + return 1 + } +} + +# min +# Return the smaller of two numbers +# +# Arguments: +# a First operand +# b Second operand +# +# Result: +# smaller of a or b +# +proc ::math::decimal::min {a b} { + foreach {sa ma ea} $a {break} + foreach {sb mb eb} $b {break} + + if { $sa != $sb } { + if {$ma != 0 } { + set ma 1 + set ea 0 + } elseif { $mb != 0 } { + set mb 1 + set eb 0 + } + } + if { $ma eq "Inf" && $mb eq "Inf" } { + if { $sa == $sb } { + return [list $sa "Inf" 0] + } else { + return [list 1 "Inf" 0] + } + } + + set comparison [compare [list $sa $ma $ea] [list $sb $mb $eb]] + + if { $comparison == 1 } { + return [Rescale $b] + } elseif { $comparison == -1 } { + return [Rescale $a] + } elseif { $sb != $sa } { + if { $sa } { + return [Rescale $a] + } else { + return [Rescale $b] + } + } elseif { $sb && $eb > $ea } { + # Both are negative and the same numerically. So return the one with the largest exponent. + return [Rescale $b] + } elseif { $sb } { + # Negative with $eb < $ea now. + return [Rescale $a] + } elseif { $ea > $eb } { + # Both are positive so return the one with the smaller + return [Rescale $b] + } else { + return [Rescale $a] + } +} + +# max +# Return the larger of two numbers +# +# Arguments: +# a First operand +# b Second operand +# +# Result: +# larger of a or b +# +proc ::math::decimal::max {a b} { + foreach {sa ma ea} $a {break} + foreach {sb mb eb} $b {break} + + if { $sa != $sb } { + if {$ma != 0 } { + set ma 1 + set ea 0 + } elseif { $mb != 0 } { + set mb 1 + set eb 0 + } + } + if { $ma eq "Inf" && $mb eq "Inf" } { + if { $sa == $sb } { + return [list $sa "Inf" 0] + } else { + return [list 0 "Inf" 0] + } + } + + set comparison [compare [list $sa $ma $ea] [list $sb $mb $eb]] + + if { $comparison == 1 } { + return [Rescale $a] + } elseif { $comparison == -1 } { + return [Rescale $b] + } elseif { $sb != $sa } { + if { $sa } { + return [Rescale $b] + } else { + return [Rescale $a] + } + } elseif { $sb && $eb > $ea } { + # Both are negative and the same numerically. So return the one with the smallest exponent. + return [Rescale $a] + } elseif { $sb } { + # Negative with $eb < $ea now. + return [Rescale $b] + } elseif { $ea > $eb } { + # Both are positive so return the one with the larger exponent + return [Rescale $a] + } else { + return [Rescale $b] + } +} + +# maxmag -- max-magnitude +# Return the larger of two numbers ignoring their signs. +# +# Arguments: +# a First operand +# b Second operand +# +# Result: +# larger of a or b ignoring their signs. +# +proc ::math::decimal::maxmag {a b} { + foreach {sa ma ea} $a {break} + foreach {sb mb eb} $b {break} + + + if { $ma eq "Inf" && $mb eq "Inf" } { + if { $sa == 0 || $sb == 0 } { + return [list 0 "Inf" 0] + } else { + return [list 1 "Inf" 0] + } + } + + set comparison [compare [list 0 $ma $ea] [list 0 $mb $eb]] + + if { $comparison == 1 } { + return [Rescale $a] + } elseif { $comparison == -1 } { + return [Rescale $b] + } elseif { $sb != $sa } { + if { $sa } { + return [Rescale $b] + } else { + return [Rescale $a] + } + } elseif { $sb && $eb > $ea } { + # Both are negative and the same numerically. So return the one with the smallest exponent. + return [Rescale $a] + } elseif { $sb } { + # Negative with $eb < $ea now. + return [Rescale $b] + } elseif { $ea > $eb } { + # Both are positive so return the one with the larger exponent + return [Rescale $a] + } else { + return [Rescale $b] + } +} + +# minmag -- min-magnitude +# Return the smaller of two numbers ignoring their signs. +# +# Arguments: +# a First operand +# b Second operand +# +# Result: +# smaller of a or b ignoring their signs. +# +proc ::math::decimal::minmag {a b} { + foreach {sa ma ea} $a {break} + foreach {sb mb eb} $b {break} + + if { $ma eq "Inf" && $mb eq "Inf" } { + if { $sa == 1 || $sb == 1 } { + return [list 1 "Inf" 0] + } else { + return [list 0 "Inf" 0] + } + } + + set comparison [compare [list 0 $ma $ea] [list 0 $mb $eb]] + + if { $comparison == 1 } { + return [Rescale $b] + } elseif { $comparison == -1 } { + return [Rescale $a] + } else { + # They compared the same so now we use a normal comparison including the signs. This is per the specs. + if { $sa > $sb } { + return [Rescale $a] + } elseif { $sb > $sa } { + return [Rescale $b] + } elseif { $sb && $eb > $ea } { + # Both are negative and the same numerically. So return the one with the largest exponent. + return [Rescale $b] + } elseif { $sb } { + # Negative with $eb < $ea now. + return [Rescale $a] + } elseif { $ea > $eb } { + return [Rescale $b] + } else { + return [Rescale $a] + } + } +} + +# fma - fused-multiply-add +# Takes three operands. Multiplies the first two and then adds the third. +# Only one rounding (Rescaling) takes place at the end instead of after +# both the multiplication and again after the addition. +# +# Arguments: +# a First operand +# b Second operand +# c Third operand +# +# Result: +# (a*b)+c +# +proc ::math::decimal::fused-multiply-add {a b c} { + return [fma $a $b $c] +} + +proc ::math::decimal::fma {a b c} { + return [+ $c [* $a $b 0]] +} + +# multiply or * +# Multiply two numbers +# +# Arguments: +# a First operand +# b Second operand +# +# Result: +# Product of both (rescaled) +# +proc ::math::decimal::multiply {a b {rescale 1}} { + return [* $a $b $rescale] +} + +proc ::math::decimal::* {a b {rescale 1}} { + foreach {sa ma ea} $a {break} + foreach {sb mb eb} $b {break} + + if { $ma eq "NaN" || $mb eq "NaN" } { + return [list 0 "NaN" 0] + } + + set sr [expr {$sa^$sb}] + + if { $ma eq "Inf" || $mb eq "Inf" } { + if { $ma == 0 || $mb == 0 } { + return [list 0 "NaN" 0] + } else { + return [list $sr "Inf" 0] + } + } + + set mr [expr {$ma * $mb}] + set er [expr {$ea + $eb}] + + + if { $rescale } { + return [Rescale [list $sr $mr $er]] + } else { + return [list $sr $mr $er] + } +} + +# divide or / +# Divide two numbers +# +# Arguments: +# a First operand +# b Second operand +# +# Result: +# Quotient of both (rescaled) +# +proc ::math::decimal::divide {a b {rescale 1}} { + return [/ $a $b] +} + +proc ::math::decimal::/ {a b {rescale 1}} { + variable precision + + foreach {sa ma ea} $a {break} + foreach {sb mb eb} $b {break} + + if { $ma eq "NaN" || $mb eq "NaN" } { + return [list 0 "NaN" 0] + } + + set sr [expr {$sa^$sb}] + + if { $ma eq "Inf" } { + if { $mb ne "Inf"} { + return [list $sr "Inf" 0] + } else { + return [list 0 "NaN" 0] + } + } + + if { $mb eq "Inf" } { + if { $ma ne "Inf"} { + return [list $sr 0 0] + } else { + return [list 0 "NaN" 0] + } + } + + if { $mb == 0 } { + if { $ma == 0 } { + return [list 0 "NaN" 0] + } else { + return [list $sr "Inf" 0] + } + } + set adjust 0 + set mr 0 + + + if { $ma == 0 } { + set er [expr {$ea - $eb}] + return [list $sr 0 $er] + } + if { $ma < $mb } { + while { $ma < $mb } { + set ma [expr {$ma * 10}] + incr adjust + } + } elseif { $ma >= $mb * 10 } { + while { $ma >= [expr {$mb * 10}] } { + set mb [expr {$mb * 10}] + incr adjust -1 + } + } + + while { 1 } { + while { $mb <= $ma } { + set ma [expr {$ma - $mb}] + incr mr + } + if { ( $ma == 0 && $adjust >= 0 ) || [string length $mr] > $precision + 1 } { + break + } else { + set ma [expr {$ma * 10}] + set mr [expr {$mr * 10}] + incr adjust + } + } + + set er [expr {$ea - ($eb + $adjust)}] + + if { $rescale } { + return [Rescale [list $sr $mr $er]] + } else { + return [list $sr $mr $er] + } +} + +# divideint -- Divide integer +# Divide a by b and return the integer part of the division. +# +# Basically, if we send a and b to the divideint (which returns i) +# and remainder function (which returns r) then the following is true: +# a = i*b + r +# +# Arguments: +# a First operand +# b Second operand +# +# +proc ::math::decimal::divideint { a b } { + foreach {sa ma ea} $a {break} + foreach {sb mb eb} $b {break} + set sr [expr {$sa^$sb}] + + + + if { $sr == 1 } { + set sign_string "-" + } else { + set sign_string "" + } + + if { ($ma eq "NaN" || $mb eq "NaN") || ($ma == 0 && $mb == 0 ) } { + return "NaN" + } + + if { $ma eq "Inf" || $mb eq "Inf" } { + if { $ma eq $mb } { + return "NaN" + } elseif { $mb eq "Inf" } { + return "${sign_string}0" + } else { + return "${sign_string}Inf" + } + } + + if { $mb == 0 } { + return "${sign_string}Inf" + } + if { $mb == "Inf" } { + return "${sign_string}0" + } + set adjust [expr {abs($ea - $eb)}] + if { $ea < $eb } { + set a_adjust 0 + set b_adjust $adjust + } elseif { $ea > $eb } { + set b_adjust 0 + set a_adjust $adjust + } else { + set a_adjust 0 + set b_adjust 0 + } + + set integer [expr {($ma*10**$a_adjust)/($mb*10**$b_adjust)}] + return $sign_string$integer +} + +# remainder -- Remainder from integer division. +# Divide a by b and return the remainder part of the division. +# +# Basically, if we send a and b to the divideint (which returns i) +# and remainder function (which returns r) then the following is true: +# a = i*b + r +# +# Arguments: +# a First operand +# b Second operand +# +# +proc ::math::decimal::remainder { a b } { + foreach {sa ma ea} $a {break} + foreach {sb mb eb} $b {break} + + if { $sa == 1 } { + set sign_string "-" + } else { + set sign_string "" + } + + if { ($ma eq "NaN" || $mb eq "NaN") || ($ma == 0 && $mb == 0 ) } { + if { $mb eq "NaN" && $mb ne $ma } { + if { $sb == 1 } { + set sign_string "-" + } else { + set sign_string "" + } + return "${sign_string}NaN" + } elseif { $ma eq "NaN" } { + return "${sign_string}NaN" + } else { + return "NaN" + } + } elseif { $mb == 0 } { + return "NaN" + } + + if { $ma eq "Inf" || $mb eq "Inf" } { + if { $ma eq $mb } { + return "NaN" + } elseif { $mb eq "Inf" } { + return [tostr $a] + } else { + return "NaN" + } + } + + if { $mb == 0 } { + return "${sign_string}Inf" + } + if { $mb == "Inf" } { + return "${sign_string}0" + } + + lset a 0 0 + lset b 0 0 + if { $mb == 0 } { + return "${sign_string}Inf" + } + if { $mb == "Inf" } { + return "${sign_string}0" + } + + set adjust [expr {abs($ea - $eb)}] + if { $ea < $eb } { + set a_adjust 0 + set b_adjust $adjust + } elseif { $ea > $eb } { + set b_adjust 0 + set a_adjust $adjust + } else { + set a_adjust 0 + set b_adjust 0 + } + + set integer [expr {($ma*10**$a_adjust)/($mb*10**$b_adjust)}] + + set remainder [tostr [- $a [* [fromstr $integer] $b 0]]] + return $sign_string$remainder +} + + +# abs -- +# Returns the Absolute Value of a number +# +# Arguments: +# Number in the form of {sign mantisse exponent} +# +# Result: +# Absolute value (as a list) +# + proc ::math::decimal::abs {a} { + lset a 0 0 + return [Rescale $a] + } + + +# Rescale -- +# Rescale the number (using proper rounding) +# +# Arguments: +# a Number in decimal format +# +# Result: +# Rescaled number +# +proc ::math::decimal::Rescale { a } { + + + + variable precision + variable rounding + variable maxExponent + variable minExponent + variable tinyExponent + + foreach {sign mantisse exponent} $a {break} + + set man_length [string length $mantisse] + + set adjusted_exponent [expr {$exponent + ($man_length -1)}] + + if { $adjusted_exponent < $tinyExponent } { + set mantisse [lindex [round_$rounding [list $sign $mantisse [expr {abs($tinyExponent) - abs($adjusted_exponent)}]] 0] 1] + return [list $sign $mantisse $tinyExponent] + } elseif { $adjusted_exponent > $maxExponent } { + if { $mantisse == 0 } { + return [list $sign 0 $maxExponent] + } else { + switch -- $rounding { + half_even - + half_up { return [list $sign "Inf" 0] } + down - + 05up { + return [list $sign [string repeat 9 $precision] $maxExponent] + } + ceiling { + if { $sign } { + return [list $sign [string repeat 9 $precision] $maxExponent] + } else { + return [list 0 "Inf" 0] + } + } + floor { + if { !$sign } { + return [list $sign [string repeat 9 $precision] $maxExponent] + } else { + return [list 1 "Inf" 0] + } + } + default { } + } + } + } + + if { $man_length <= $precision } { + return [list $sign $mantisse $exponent] + } + + set mantisse [lindex [round_$rounding [list $sign $mantisse [expr {$precision - $man_length}]] 0] 1] + set exponent [expr {$exponent + ($man_length - $precision)}] + + # it is possible now that our rounding gave us a new digit in our mantisse + # example rounding 999.9 to 1 digits with precision 3 will give us + # 1000 back. + # This can only happen by adding a zero on the end of our mantisse however. + # So we just chomp it off. + + set man_length_now [string length $mantisse] + if { $man_length_now > $precision } { + set mantisse [string range $mantisse 0 end-1] + incr exponent + # Check again to see if we have overflowed + # we change our test to >= because we have incremented exponent. + if { $adjusted_exponent >= $maxExponent } { + switch -- $rounding { + half_even - + half_up { return [list $sign "Inf" 0] } + down - + 05up { + return [list $sign [string repeat 9 $precision] $maxExponent] + } + ceiling { + if { $sign } { + return [list $sign [string repeat 9 $precision] $maxExponent] + } else { + return [list 0 "Inf" 0] + } + } + floor { + if { !$sign } { + return [list $sign [string repeat 9 $precision] $maxExponent] + } else { + return [list 1 "Inf" 0] + } + } + default { } + } + } + } + return [list $sign $mantisse $exponent] +} + +# tostr -- +# Convert number to string using appropriate method depending on extended +# attribute setting. +# +# Arguments: +# number Number to be converted +# +# Result: +# Number in the form of a string +# +proc ::math::decimal::tostr { number } { + variable extended + switch -- $extended { + 0 { return [tostr_numeric $number] } + 1 { return [tostr_scientific $number] } + } +} + +# tostr_scientific -- +# Convert number to string using scientific notation as called for in +# Decmath specifications. +# +# Arguments: +# number Number to be converted +# +# Result: +# Number in the form of a string +# +proc ::math::decimal::tostr_scientific {number} { + foreach {sign mantisse exponent} $number {break} + + if { $sign } { + set sign_string "-" + } else { + set sign_string "" + } + + if { $mantisse eq "NaN" } { + return "NaN" + } + if { $mantisse eq "Inf" } { + return ${sign_string}${mantisse} + } + + + set digits [string length $mantisse] + set adjusted_exponent [expr {$exponent + $digits - 1}] + + # Why -6? Go read the specs on the website mentioned in the header. + # They choose it, I'm using it. They actually list some good reasons though. + if { $exponent <= 0 && $adjusted_exponent >= -6 } { + if { $exponent == 0 } { + set string $mantisse + } else { + set exponent [expr {abs($exponent)}] + if { $digits > $exponent } { + set string [string range $mantisse 0 [expr {$digits-$exponent-1}]].[string range $mantisse [expr {$digits-$exponent}] end] + set exponent [expr {-$exponent}] + } else { + set string 0.[string repeat 0 [expr {$exponent-$digits}]]$mantisse + } + } + } elseif { $exponent <= 0 && $adjusted_exponent < -6 } { + if { $digits > 1 } { + + set string [string range $mantisse 0 0].[string range $mantisse 1 end] + + set exponent [expr {$exponent + $digits - 1}] + set string "${string}E${exponent}" + } else { + set string "${mantisse}E${exponent}" + } + } else { + if { $adjusted_exponent >= 0 } { + set adjusted_exponent "+$adjusted_exponent" + } + if { $digits > 1 } { + set string "[string range $mantisse 0 0].[string range $mantisse 1 end]E$adjusted_exponent" + } else { + set string "${mantisse}E$adjusted_exponent" + } + } + return $sign_string$string +} + +# tostr_numeric -- +# Convert number to string using the simplified number set conversion +# from the X3.274 subset of Decimal Arithmetic specifications. +# +# Arguments: +# number Number to be converted +# +# Result: +# Number in the form of a string +# +proc ::math::decimal::tostr_numeric {number} { + variable precision + foreach {sign mantisse exponent} $number {break} + + if { $sign } { + set sign_string "-" + } else { + set sign_string "" + } + + if { $mantisse eq "NaN" } { + return "NaN" + } + if { $mantisse eq "Inf" } { + return ${sign_string}${mantisse} + } + + set digits [string length $mantisse] + set adjusted_exponent [expr {$exponent + $digits - 1}] + + if { $mantisse == 0 } { + set string 0 + set sign_string "" + } elseif { $exponent <= 0 && $adjusted_exponent >= -6 } { + if { $exponent == 0 } { + set string $mantisse + } else { + set exponent [expr {abs($exponent)}] + if { $digits > $exponent } { + set string [string range $mantisse 0 [expr {$digits-$exponent-1}]] + set decimal_part [string range $mantisse [expr {$digits-$exponent}] end] + set string ${string}.${decimal_part} + set exponent [expr {-$exponent}] + } else { + set string 0.[string repeat 0 [expr {$exponent-$digits}]]$mantisse + } + } + } elseif { $exponent <= 0 && $adjusted_exponent < -6 } { + if { $digits > 1 } { + set string [string range $mantisse 0 0].[string range $mantisse 1 end] + set exponent [expr {$exponent + $digits - 1}] + set string "${string}E${exponent}" + } else { + set string "${mantisse}E${exponent}" + } + } else { + if { $adjusted_exponent >= 0 } { + set adjusted_exponent "+$adjusted_exponent" + } + if { $digits > 1 && $adjusted_exponent >= $precision } { + set string "[string range $mantisse 0 0].[string range $mantisse 1 end]E$adjusted_exponent" + } elseif { $digits + $exponent <= $precision } { + set string ${mantisse}[string repeat 0 [expr {$exponent}]] + } else { + set string "${mantisse}E$adjusted_exponent" + } + } + return $sign_string$string +} + +# fromstr -- +# Convert string to number +# +# Arguments: +# string String to be converted +# +# Result: +# Number in the form of {sign mantisse exponent} +# +proc ::math::decimal::fromstr {string} { + variable extended + + set string [string trim $string "'\""] + + if { [string range $string 0 0] == "-" } { + set sign 1 + set string [string trimleft $string -] + incr pos -1 + } else { + set sign 0 + } + + if { $string eq "Inf" || $string eq "NaN" } { + if {!$extended} { + # we don't allow these strings in the subset arithmetic. + # throw error. + error "Infinities and NaN's not allowed in simplified decimal arithmetic" + } else { + return [list $sign $string 0] + } + } + + set string [string trimleft $string "+-"] + set echeck [string first "E" [string toupper $string]] + set epart 0 + if { $echeck >= 0 } { + set epart [string range $string [expr {$echeck+1}] end] + set string [string range $string 0 [expr {$echeck -1}]] + } + + set pos [string first . $string] + + if { $pos < 0 } { + if { $string == 0 } { + set mantisse 0 + if { !$extended } { + set sign 0 + } + } else { + set mantisse $string + } + set exponent 0 + } else { + if { $string == "" } { + return [list 0 0 0] + } else { + #stripping the leading zeros here is required to avoid some octal issues. + #However, it causes us to fail some tests with numbers like 0.00 and 0.0 + #which test differently but we can't deal with now. + set mantisse [string trimleft [string map {. ""} $string] 0] + if { $mantisse == "" } { + set mantisse 0 + if {!$extended} { + set sign 0 + } + } + set fraction [string range $string [expr {$pos+1}] end] + set exponent [expr {-[string length $fraction]}] + } + } + set exponent [expr {$exponent + $epart}] + + if { $extended } { + return [list $sign $mantisse $exponent] + } else { + return [Rescale [list $sign $mantisse $exponent]] + } +} + +# ipart -- +# Return the integer part of a Decimal Number +# +# Arguments: +# Number in the form of {sign mantisse exponent} +# +# +# Result: +# Integer +# +proc ::math::decimal::ipart { a } { + + foreach {sa ma ea} $a {break} + + if { $ea == 0 } { + if { $sa } { + return -$ma + } else { + return $ma + } + } elseif { $ea > 0 } { + if { $sa } { + return [expr {-1 * $ma * 10**$ea}] + } else { + return [expr {$ma * 10**$ea}] + } + } else { + if { [string length $ma] <= abs($ea) } { + return 0 + } else { + if { $sa } { + set string_sign "-" + } else { + set string_sign "" + } + set ea [expr {abs($ea)}] + return "${string_sign}[string range $ma 0 end-$ea]" + } + } +} + +# round_05_up -- +# Round zero or five away from 0. +# The same as round-up, except that rounding up only occurs +# if the digit to be rounded up is 0 or 5, and after overflow +# the result is the same as for round-down. +# +# Bias: away from zero +# +# Arguments: +# Number in the form of {sign mantisse exponent} +# Number of decimal points to round to. +# +# Result: +# Number in the form of {sign mantisse exponent} +# +proc ::math::decimal::round_05up {a digits} { + foreach {sa ma ea} $a {break} + + if { -$ea== $digits } { + return $a + } elseif { $digits + $ea > 0 } { + set mantissa [expr { $ma * 10**($digits+$ea) }] + set exponent [expr {-1 * $digits}] + } else { + set round_exponent [expr {$digits + $ea}] + if { [string length $ma] <= $round_exponent } { + if { $ma != 0 } { + set mantissa 1 + } else { + set mantissa 0 + } + set exponent 0 + } else { + set integer_part [ipart [list 0 $ma $round_exponent]] + + if { [compare [list 0 $ma $round_exponent] [list 0 ${integer_part}0 -1]] == 0 } { + # We are rounding something with fractional part .0 + set mantissa $integer_part + } elseif { [string index $integer_part end] eq 0 || [string index $integer_part end] eq 5 } { + set mantissa [expr {$integer_part + 1}] + } else { + set mantissa $integer_part + } + set exponent [expr {-1 * $digits}] + } + } + return [list $sa $mantissa $exponent] +} + +# round_half_up -- +# +# Round to the nearest. If equidistant, round up. +# +# +# Bias: away from zero +# +# Arguments: +# Number in the form of {sign mantisse exponent} +# Number of decimal points to round to. +# +# Result: +# Number in the form of {sign mantisse exponent} +# +proc ::math::decimal::round_half_up {a digits} { + foreach {sa ma ea} $a {break} + + if { $digits + $ea == 0 } { + return $a + } elseif { $digits + $ea > 0 } { + set mantissa [expr {$ma *10 **($digits+$ea)}] + } else { + set round_exponent [expr {$digits + $ea}] + set integer_part [ipart [list 0 $ma $round_exponent]] + + switch -- [compare [list 0 $ma $round_exponent] [list 0 ${integer_part}5 -1]] { + 0 { + # We are rounding something with fractional part .5 + set mantissa [expr {$integer_part + 1}] + } + -1 { + set mantissa $integer_part + } + 1 { + set mantissa [expr {$integer_part + 1}] + } + + } + } + set exponent [expr {-1 * $digits}] + return [list $sa $mantissa $exponent] +} + +# round_half_even -- +# Round to the nearest. If equidistant, round so the final digit is even. +# Bias: none +# +# Arguments: +# Number in the form of {sign mantisse exponent} +# Number of decimal points to round to. +# +# Result: +# Number in the form of {sign mantisse exponent} +# +proc ::math::decimal::round_half_even {a digits} { + + foreach {sa ma ea} $a {break} + + if { $digits + $ea == 0 } { + return $a + } elseif { $digits + $ea > 0 } { + set mantissa [expr {$ma * 10**($digits+$ea)}] + } else { + set round_exponent [expr {$digits + $ea}] + set integer_part [ipart [list 0 $ma $round_exponent]] + + switch -- [compare [list 0 $ma $round_exponent] [list 0 ${integer_part}5 -1]] { + 0 { + # We are rounding something with fractional part .5 + if { $integer_part % 2 } { + # We are odd so round up + set mantissa [expr {$integer_part + 1}] + } else { + # We are even so round down + set mantissa $integer_part + } + } + -1 { + set mantissa $integer_part + } + 1 { + set mantissa [expr {$integer_part + 1}] + } + } + } + set exponent [expr {-1 * $digits}] + return [list $sa $mantissa $exponent] +} + +# round_half_down -- +# +# Round to the nearest. If equidistant, round down. +# +# Bias: towards zero +# +# Arguments: +# Number in the form of {sign mantisse exponent} +# Number of decimal points to round to. +# +# Result: +# Number in the form of {sign mantisse exponent} +# +proc ::math::decimal::round_half_down {a digits} { + foreach {sa ma ea} $a {break} + + if { $digits + $ea == 0 } { + return $a + } elseif { $digits + $ea > 0 } { + set mantissa [expr {$ma * 10**($digits+$ea)}] + } else { + set round_exponent [expr {$digits + $ea}] + set integer_part [ipart [list 0 $ma $round_exponent]] + switch -- [compare [list 0 $ma $round_exponent] [list 0 ${integer_part}5 -1]] { + 0 { + # We are rounding something with fractional part .5 + # The rule is to round half down. + set mantissa $integer_part + } + -1 { + set mantissa $integer_part + } + 1 { + set mantissa [expr {$integer_part + 1}] + } + } + } + set exponent [expr {-1 * $digits}] + return [list $sa $mantissa $exponent] +} + +# round_down -- +# +# Round toward 0. (Truncate) +# +# Bias: towards zero +# +# Arguments: +# Number in the form of {sign mantisse exponent} +# Number of decimal points to round to. +# +# Result: +# Number in the form of {sign mantisse exponent} +# +proc ::math::decimal::round_down {a digits} { + foreach {sa ma ea} $a {break} + + + if { -$ea== $digits } { + return $a + } elseif { $digits + $ea > 0 } { + set mantissa [expr { $ma * 10**($digits+$ea) }] + } else { + set round_exponent [expr {$digits + $ea}] + set mantissa [ipart [list 0 $ma $round_exponent]] + } + + set exponent [expr {-1 * $digits}] + return [list $sa $mantissa $exponent] +} + +# round_floor -- +# +# Round toward -Infinity. +# +# Bias: down toward -Inf. +# +# Arguments: +# Number in the form of {sign mantisse exponent} +# Number of decimal points to round to. +# +# Result: +# Number in the form of {sign mantisse exponent} +# +proc ::math::decimal::round_floor {a digits} { + foreach {sa ma ea} $a {break} + + if { -$ea== $digits } { + return $a + } elseif { $digits + $ea > 0 } { + set mantissa [expr { $ma * 10**($digits+$ea) }] + } else { + set round_exponent [expr {$digits + $ea}] + if { $ma == 0 } { + set mantissa 0 + } elseif { !$sa } { + set mantissa [ipart [list 0 $ma $round_exponent]] + } else { + set mantissa [expr {[ipart [list 0 $ma $round_exponent]] + 1}] + } + } + set exponent [expr {-1 * $digits}] + return [list $sa $mantissa $exponent] +} + +# round_up -- +# +# Round away from 0 +# +# Bias: away from 0 +# +# Arguments: +# Number in the form of {sign mantisse exponent} +# Number of decimal points to round to. +# +# Result: +# Number in the form of {sign mantisse exponent} +# +proc ::math::decimal::round_up {a digits} { + foreach {sa ma ea} $a {break} + + + if { -$ea== $digits } { + return $a + } elseif { $digits + $ea > 0 } { + set mantissa [expr { $ma * 10**($digits+$ea) }] + set exponent [expr {-1 * $digits}] + } else { + set round_exponent [expr {$digits + $ea}] + if { [string length $ma] <= $round_exponent } { + if { $ma != 0 } { + set mantissa 1 + } else { + set mantissa 0 + } + set exponent 0 + } else { + set integer_part [ipart [list 0 $ma $round_exponent]] + switch -- [compare [list 0 $ma $round_exponent] [list 0 ${integer_part}0 -1]] { + 0 { + # We are rounding something with fractional part .0 + set mantissa $integer_part + } + default { + set mantissa [expr {$integer_part + 1}] + } + } + set exponent [expr {-1 * $digits}] + } + } + return [list $sa $mantissa $exponent] +} + +# round_ceiling -- +# +# Round toward Infinity +# +# Bias: up toward Inf. +# +# Arguments: +# Number in the form of {sign mantisse exponent} +# Number of decimal points to round to. +# +# Result: +# Number in the form of {sign mantisse exponent} +# +proc ::math::decimal::round_ceiling {a digits} { + foreach {sa ma ea} $a {break} + if { -$ea== $digits } { + return $a + } elseif { $digits + $ea > 0 } { + set mantissa [expr { $ma * 10**($digits+$ea) }] + set exponent [expr {-1 * $digits}] + } else { + set round_exponent [expr {$digits + $ea}] + if { [string length $ma] <= $round_exponent } { + if { $ma != 0 } { + set mantissa 1 + } else { + set mantissa 0 + } + set exponent 0 + } else { + set integer_part [ipart [list 0 $ma $round_exponent]] + switch -- [compare [list 0 $ma $round_exponent] [list 0 ${integer_part}0 -1]] { + 0 { + # We are rounding something with fractional part .0 + set mantissa $integer_part + } + default { + if { $sa } { + set mantissa [expr {$integer_part}] + } else { + set mantissa [expr {$integer_part + 1}] + } + } + } + set exponent [expr {-1 * $digits}] + } + } + + return [list $sa $mantissa $exponent] +} + +# is-finite +# +# Takes one operand and returns: 1 if neither Inf or Nan otherwise 0. +# +# +# Arguments: +# a - decimal number +# +# Returns: +# +proc ::math::decimal::is-finite { a } { + set mantissa [lindex $a 1] + if { $mantissa == "Inf" || $mantissa == "NaN" } { + return 0 + } else { + return 1 + } +} + +# is-infinite +# +# Takes one operand and returns: 1 if Inf otherwise 0. +# +# +# Arguments: +# a - decimal number +# +# Returns: +# +proc ::math::decimal::is-infinite { a } { + set mantissa [lindex $a 1] + if { $mantissa == "Inf" } { + return 1 + } else { + return 0 + } +} + +# is-NaN +# +# Takes one operand and returns: 1 if NaN otherwise 0. +# +# +# Arguments: +# a - decimal number +# +# Returns: +# +proc ::math::decimal::is-NaN { a } { + set mantissa [lindex $a 1] + if { $mantissa == "NaN" } { + return 1 + } else { + return 0 + } +} + +# is-signed +# +# Takes one operand and returns: 1 if sign is 1 (negative). +# +# +# Arguments: +# a - decimal number +# +# Returns: +# +proc ::math::decimal::is-signed { a } { + set sign [lindex $a 0] + if { $sign } { + return 1 + } else { + return 0 + } +} + +# is-zero +# +# Takes one operand and returns: 1 if operand is zero otherwise 0. +# +# +# Arguments: +# a - decimal number +# +# Returns: +# +proc ::math::decimal::is-zero { a } { + set mantisse [lindex $a 1] + if { $mantisse == 0 } { + return 1 + } else { + return 0 + } +} |