Version 19 of A little calculator

Updated 2004-02-06 23:03:49

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


Richard Suchenwirth - 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 ;-)

 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; append e " = $res"} ;# RS

NEM suggests changing the binding to:

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

to allow easy typing in of a new calculation without having to delete everything first. 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


Arts and crafts of Tcl-Tk programming - Category Mathematics - Category Application