« RPN Calculator in PowerShell | Main | Random Slashdot Stuff for Auction on eBay »
October 17, 2007
Hacking on the PowerShell RPN Calculator
I couldn't resist "improving" the calculator by making it more generic. I put quotes around "improving" because it is possible to overdo something like this. Right now the code is a bit harder to read than the earlier version but it also is much more extensible, so I think it's OK. It has four hashtables maping operators to .NET functions; there are four because it distinguishes between functions on the Math class and functions on the Decimal class, and between methods that take one parameter and ones that take two. I would make that data-driven also if I tried, but that might be overkill. In fact I could make it generically take any unknown token it finds and try to invoke a matching method on the Math or Decimal classes...I also haven't quite figured out how to get the static properties programmatically, so I had to hardcode in e, pi, max and min. This may be due to bugs in PowerShell 1.0; I know there is a bug where you should be able to write:
$add = [Decimal]::Add
$add.Invoke(2,3)
but it doesn't work. That's not exactly the same as getting the value of a static property, but maybe there's a related issue, or maybe I just can't figure out how to do it right.
Anyway, this is the code right now:
$s = new-object System.Collections.Stack $n = new-object System.Decimal $df1 = @{ "neg" = "Negate" "floor" = "Floor" "ceil" = "Ceiling" "round" = "Round" } $df2 = @{ "+" = "Add" ; "-" = "Subtract" ; "*" = "Multiply" ; "/" = "Divide" ; "mod" = "Remainder" } $mf1 = @{ "cos" = "Cos" ; "sqrt" = "Sqrt" ; "exp" = "Exp" } $mf2 = @{ "^" = "Pow" ; "log" = "Log" } foreach ($a in $args) { switch ($a) { { $df1.$a } { $s.Push( [Decimal].GetMethod($df1.$a,@([Decimal])).Invoke( $null, @($s.Pop()))) } { $df2.$a } { $temp = $s.Pop() $s.Push( [Decimal].GetMethod($df2.$a,@([Decimal],[Decimal])).Invoke( $null, @($s.Pop(),$temp))) } { $mf1.$a } { $s.Push( [Convert]::ToDecimal( [Math].GetMethod($mf1.$a,@([Double])).Invoke( $null, @([Decimal]::ToDouble($s.Pop()))))) } { $mf2.$a } { $temp = [Decimal]::ToDouble($s.Pop()) $s.Push( [Convert]::ToDecimal( [Math].GetMethod($mf2.$a,@([Double],[Double])).Invoke( $null, @([Decimal]::ToDouble($s.Pop()),$temp)))) } "e" { $s.Push([Convert]::ToDecimal([Math]::E)) } "pi" { $s.Push([Convert]::ToDecimal([Math]::PI)) } "max" { $s.Push([Decimal]::MaxValue) } "min" { $s.Push([Decimal]::MinValue) } "dup" { $s.Push($s.Peek()) } { [Decimal]::TryParse($a,[ref]$n) } { $s.Push($n) } } } $s
(Weird random fact: when I removed the "e" from the variable name $temp, Movable Type returned an error trying to post this. In fact just including the t m p word anywhere seems to mess it up.) It gets a bit deep in the indenting; I decided that the calls to GetMethod() would not be worthy of their own indent because they were resolve before the end--that is, the closing parenthesis for those calls happens before the chain of ))) at the end of the multi-line statement.
Because it mixes Math and Decimal it doesn't always work up to the size of Decimal, which is 2^96-1. So something like:
./rpn2 2 95 ^ dup 1 - +
actually throws an exception because the number winds up being an overflow due to rounding; you can compare:
./rpn2 2 95 ^
which uses the Math and returns
39614081257132200000000000000
to
./rpn2 max 2 /
which returns the slightly smaller
39614081257132168796771975168
Actually that last number is 2 ^ 95 exactly, when it should really be 2 ^ 95 - 1, but I guess there is some rounding or something at the limit.
A tip of the hat to Bruce Payette for some help getting this working.
Posted by AdamBa at October 17, 2007 11:07 PM
Trackback Pings
TrackBack URL for this entry:
http://proudlyserving.com/cgi-bin/mt-tb.cgi/627
Comments
My mindful of the lure of premature generalization, my young padawan.
Posted by: Andrew at October 18, 2007 08:42 AM
I think a wise Jedi Master once said, "Premature generalization is the root of all evil, unless it leads to code with six right parentheses in a row, in which case it's kinda cool."
- adam
Posted by: Adam Barr at October 18, 2007 10:32 AM