A little calculator

Richard Suchenwirth 2001-02-14 - As yet another midnight project, here's a small calculator in Tcl/Tk. An original problem (arithmetics is done with expr, so e.g. 1/2= comes out as 0) was fixed (see discussion below). In addition to the buttons you can use any of expr's other functionalities via keyboard input. (As the screenshot shows, it runs also on a CE ;-)

http://www.eteamz.com/cmym/images/calculator.jpg


 package require Tk
 wm title . Calculator
 grid [entry .e -textvar e -just right] -columnspan 5
 bind .e <Return> =
 set n 0
 foreach row {
    {7 8 9 + -}
    {4 5 6 * /}
    {1 2 3 ( )}
    {C 0 . =  }
 } {
    foreach key $row {
        switch -- $key {
            =       {set cmd =}
            C       {set cmd {set clear 1; set e ""}}
            default {set cmd "hit $key"}
        }
        lappend keys [button .[incr n] -text $key -command $cmd]
    }
    eval grid $keys -sticky we ;#-padx 1 -pady 1
    set keys [list]
 }
 grid .$n -columnspan 2 ;# make last key (=) double wide
 proc = {} {
    regsub { =.+} $::e "" ::e ;# maybe clear previous result
    if [catch {lappend ::e = [set ::res [expr 1.0*$::e]]}] {
        .e config -fg red
    }
    .e xview end
    set ::clear 1
 }
 proc hit {key} {
    if $::clear {
        set ::e ""
        if ![regexp {[0-9().]} $key] {set ::e $::res}
        .e config -fg black
        .e icursor end
        set ::clear 0
    }
    .e insert end $key
 }
 set clear 0
 focus .e           ;# allow keyboard input
 wm resizable . 0 0

Rob Hegt - By adding the below 'fix' proc and changing the line doing the expr to the one given below, it is no longer necessary to add a . in case of a division.

 proc fix {str} {
  if {[string first "." "$str"] < 0} {
    if {[string first "/" "$str"] > -1} {
      set str "$str."
    }
  }
  return "$str"
 }
…
    if [catch {lappend ::e = [set ::res [expr [fix $::e]]]}] {

RS: Yes, but this fix raises an error with well-formed expressions like 1/(2+3). It is simpler, and more bullet-proof, to just prefix 1.0* to the expression when giving it to expr.

2-4-2004 MDD: How does one get around the internal int conversion in, for example, 1 / (2 * 4 / 5), which produces an answer of 1.0, rather than the correct answer of 0.625 ? One way to do it might be to go through the entire formula string and replace all ints with doubles, prior to passing it to expr, but that seems a little clunky. Anybody have a cleaner way to do it?

RS: Thank you for finding this subtle bug! Isn't Wiki code review great?- I admit that prefixing 1.0* is not bulletproof enough: evaluation goes not always from left to right, parenthesized sub-expressions come first. The precision loss occurs only with division, so it is sufficient to force at least one operand of "/" to double. The following modification passes your test, and I can't presently think of situations where it would produce bad syntax:

 set ::res [expr [string map {/ *1.0/} $::e]]

At least it's short, compared to the alternative of tokenizing and parsing the expression, and applying a double() to one of "/"'s operands...

MDD: That seems to do it. Thanks.


Programmable scientific calculator in two lines: (why, it can do asin and log ;-)

 pack [entry .e -textvar e -width 50]
 bind .e <Return> {catch {expr [string map {/ *1./} $e]} res; set e $res} ;# RS & FR

FR suggests changing the binding to:

 bind .e <Return> {catch {expr [string map {/ *1./} $e]} res; set e $res;.e selection range 0 end}

to allow easy typing in of a new calculation without having to delete everything first. RS: In principle yes, but the standard selection on Windows (-fg white -bg darkblue} makes the result much less conspicuous... Also, the ::clear variable makes sure that the entry is cleared when you start a new calculation; but you many also edit the original, or re-use its result by typing an operator.

And, as CL noted, this thingy is programmable: enter for example

 [proc fac x {expr {$x<2? 1: $x*[fac [incr x -1]]}}]

into the entry, disregard warnings; now you can do

 [fac 10]

and receive [fac 10] = 3628800.0 as result...


See also: Programmable RPN calculator - t-Calc


AM I used to use a UNIX program, called bc, that allows you to type in expressions like "1+2.3" and then you get the result, 3.3. But that is not available on PC ... so, I wrote me a little Tcl script a few years ago to compensate for that. I have revised it, as one of its features was not very satisfactory. Here it is: A bc-like calculator


Derek Peschel 2005-04-27 See A fancier little calculator. The code has big bugs, so I hope a separate page will make reviewing/debugging easier. I wanted to leave a nice finished present but I'm too new to Tcl so I need help. But there are still some potentially good ideas.


Lectus - 2011-04-30 09:26:47

Here is another simple programmable calculator written using Itcl:

package req math
package req Itcl
namespace import itcl::*

proc func {name arguments body} {
        proc tcl::mathfunc::$name $arguments [list expr $body]
}

class calculator {
# if we pass init arg we're using it as a standalone program.
        constructor {{init '}} {
                if {$init eq "init"} {
                        calculator::REPL
                }
        }

        method eval {line} {
                if {$line eq "exit"} {exit} 
        
                set funcname {}
                set funcargs {}
                set funcbody {}
                if { [regexp {(.+)\((.+)\)=(.+)} $line -> funcname funcargs funcbody] } {
                        func $funcname $funcargs $funcbody
                        return $funcname
                } else {
                        if {[catch {expr $line} res]} {
                                puts stderr "Error in expression!"
                                return 0
                        } else { return $res }
                }
        }
                
        method read {} {
                gets stdin line
                calculator::eval $line
        }
                
        method REPL {} {
                while 1 {
                        puts -nonewline "\n>> "
                        puts [calculator::read]
                }
        }
}

calculator calc init

Usage:

$ tclsh calc.tcl

>> f(x y)= $x > $y ? $x : $y
f

>> f(2,3)
3

>> f(5,1)
5

>> g(x)=$x*2
g

>> 1 + g(5)
11

Explanation: I used an Itcl class to create a reusable calculator. The eval method evaluates expressions, and if the expression is a function it will define it in tcl::mathfunc namespace, so that it's available in expr. The REPL method defines the prompt that keeps reading and evaluating expressions. Type 'exit' to exit the calculator.