Version 52 of Engineering Notation

Updated 2006-12-16 00:16:01

A way to convert any arbitrary number to engineering notation [L1 ]

 # eng - return engineering notation of any given number in a 2 element list as {num eng_unit}
 # pico nano micro milli 1 kilo Mega Giga Tera
 # use: eng <num> <unit>   eg. eng 123.456E6 Hz
 # First - scan to check if n is a number, if not then just return with input given {n u}
 proc eng {n u} {
        if ![scan $n %g res] {return [list $n {} $u]}
        if [expr $n>=1E-12 && $n<1E-9] {list [expr $n*1E12] p$u} \
        elseif [expr $n>=1E-9 && $n<1E-6] {list [expr $n*1E9] n$u} \
        elseif [expr $n>=1E-6 && $n<1E-3] {list [expr $n*1E6] u$u} \
        elseif [expr $n>=1E-3 && $n<1] {list [expr $n*1E3] m$u} \
        elseif [expr $n>=1 && $n<1E3] {list [format %g $n] $u} \
        elseif [expr $n>=1E3 && $n<1E6] {list [expr $n/1E3] k$u} \
        elseif [expr $n>=1E6 && $n<1E9] {list [expr $n/1E6] M$u} \
        elseif [expr $n>=1E9 && $n<1E12] {list [expr $n/1E9] G$u} \
        elseif [expr $n>=1E12 && $n<1E15] {list [expr $n/1E12] T$u} \
        else {list [format %g $n] $u}
 }
 ### Example Output
 %eng 1.23456E-2 V
 12.3456 mV
 %eng 209357.209857E5 Hz
 20.9357209857 GHz
 %eng 0.012e-7 H
 1.2 nH

CvK (Dec 2006) - is there any way this could be simplified ?

Bryan Oakley notes: that's a lot of unbraced expressions! MJ - even more so if you consider that if and elseif pass the first following argument to expr as well. Always brace your expr-essions.

For that matter, "if [expr ..." is a bit redundant.

AMG: The rampant indenting was hurting my eyes, so I removed it. This version should be a bit easier to read.

MJ - Try the following (with the additional benefit that it is easier to add more powers of 1000):

 proc eng {num u} {
        array set orders {
         -8 y -7 z -6 a -5 f -4 p -3 n -2 u -1 m 0 {} 1 k 2 M 3 G 4 T 5 P 6 E 7 Z 8 Y
        }
        set numInfo  [split [format %e $num] e]
        set order [expr {[scan [lindex $numInfo 1] %d] / 3}]
        if {[catch {set orders($order)} prefix]} {return [list $num $u]}
        set num [expr {$num/pow(10,3*$order)}]
        return [list $num $prefix$u]
 }

CvK - MJ, Thanks for your solution. It looks better and perhaps more readable. I agree that the nested "if [expr .." is redundant. Syntax highlighting helped with the aesthetics of indentation which is lost with the pre-formatting in this wiki. I tried your example but it gave a syntax error ...

 syntax error in expression "$num/(10.**(3*$order))": unexpected operator *

MJ - That's what I get for testing under 8.5 only. In 8.5 ** is a new operator. Fixed above. BTW not only is "if [expr .." redundant, it is also slower and invokes triple substitution which can lead to very unexpected or even unsecure behaviour. Example:

 % set a 1
 % set b {$a}
 % set c {$b}
 % if [expr $c] {puts "3 times is a charm :-)"} {puts "... or not"}
 3 times a charm :-)
 % set a 0
 % if [expr $c] {puts "3 times is a charm :-)"} {puts "... or not"}
 ... or not

CvK - The speed difference as measured by [clock clicks] is about 5 fold. I measured an average of about 430 [clock clicks] for the "if..elseif" version, while MJ's "pow()..array-lookup" version measured about 80 [clock clicks], approximately 5 times faster. Not sure what units of time [clock clicks] returns, but the measure is a relative difference anyway.

NEM - clock clicks returns a system-dependent value. You can use the time command for benchmarking.

 () 3 % time {eng 209357.209857E5 Hz } 1000 ; # ifelse version
 222.984 microseconds per iteration
 () 5 % time {eng 209357.209857E5 Hz } 1000 ; # array lookup version
 88.785 microseconds per iteration

aa - After turning all the

 elseif [expr ...] {list ...} \

into

 elseif {...} {list ...} \

I found the two routines to be roughly equivalent in time taken.

CvK - Oh, Just had a wiki edit conflict! probably aa - How did you remove or change the elseif [expr... ??

While aa was saying "I found the two routines to be roughly equivalent in time taken." I was going to say - time is cool! using this measurement I see that the array lookup version is about 8 times faster!

 () 1 % info tclversion
 8.4
 () 2 % source eng.tcl; # elseif version
 () 3 % source eng2.tcl; # array lookup version
 () 4 % time {eng 209357.209857E5 Hz } 1000
 171.405 microseconds per iteration
 () 5 % time {eng2 209357.209857E5 Hz } 1000
 20.767 microseconds per iteration
 () 6 %

AMG: [time] is a marvel. Use it.

Very often I see code that contains supposed optimizations that only succeed in introducing bugs and (get this) slowing down execution. Example: At work I came across some code written by a highly competent programmer which contained a single, solitary global variable. The global was scoped to a function (i.e. "static"); it was going incognito. It cached the directional cosine matrix, following the assumption that a given latitude/longitude would be used several times in a row. When I saw this, my spider sense screamed "thread unsafe!" (which, as some will point out, is redundant), and I changed the function to recalculate the DCM every time. I thought to myself that it's always a good thing to sacrifice performance for stability, especially in my line of work. Someone else came along and profiled the code with and without my change. Guess what. Even though my version had a longer minimum execution time, it had a shorter average execution time! The original programmer's assumption about multiple calls in a row was incorrect. (One more thing: When I asked the programmer about it, he told me that he thought "static" made the variable thread-safe. Sheesh, if a man can write a correct, efficient, well-documented geodetic-geocentric coordinate conversion and utility library but can't remember the basic rules of thread safety, then I conclude that threads are beyond mortal comprehension.)

Want to make an optimization but don't know if the speedup is sufficient to justify the increased code complexity? [time] it! You might discover that your optimization actually decreases performance. Boy, wouldn't that be embarrassing? Or you might find that it cuts the time down by 90%, giving you major bragging rights. [time]! Impress your friends! Strike fear into the hearts of your foes! And best of all, it's fast. :^)

 % proc nop args {}
 % time nop 1000000
 0.256976 microseconds per iteration
 % time {time nop} 1000000
 5.92878 microseconds per iteration
 % expr {5.92878 - 0.256976}
 5.671804

CvK - removing the elseif expr.. as suggested by [aa actually slightly improves the execution time by about 35% (20uS down to 13uS) This is interesting and helps to see how Tcl works.

 () 1 % source eng.tcl ; # elseif [expr ... version
 () 2 % source eng2.tcl ; # array pow() lookup version
 () 3 % source eng3.tcl ; # elseif {..} {.. version
 () 4 % time {eng 209357.209857E5 Hz } 1000
 178.058 microseconds per iteration
 () 5 % time {eng2 209357.209857E5 Hz } 1000
 21.676 microseconds per iteration
 () 6 % time {eng3 209357.209857E5 Hz } 1000
 13.13 microseconds per iteration
 () 7 % 

BTW - what improvements does Tcl8.5 offer? See Changes in Tcl/Tk 8.5


[ Category Mathematics ]