Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I'm trying to do an interface to bc so it can be used intuitively and without the annoyance of getting "stuck" in it. I haven't got around to test it that much, because I got stuck on another detail, namely how to present the result (which is, I think, a string).

Rounding or truncating does not matter, either one is fine. Take a look below, and you'll understand immediately. I use zsh but an external tool will be just fine as I won't use this in any time or otherwise critical context, it's just a desktop tool.

calc () {
    result=`bc <<EOF
    scale=3;
    $@
    EOF`
    echo ${result//%0/} # doesn't work; will only remove one zero
                        # also, if there are only zeroes, should
                        # remove dot as well - what about .333, etc.?
}

Edit

I'm very impressed by the below solution, especially how the noglob gets away with the quotes!

But, the use of a dot to force floating point calculation is something I'll never remember (you don't use a normal calculator like that). And it is even a bit risky, especially for calculations when it's not obvious that floating point would yield an altogether different result (most likely the one you wanted).

Also, the calculations below show some un-pretty output (the too long real, and the trailing dot).

Perhaps I should combine this (some of it) with the output formatting of @Gille's answer below? When I get it to work perfectly, I'll post the result here. (Edit: The accepted answer works great. Be sure to read the comments to that answer, as well.)

calc () {
  echo $(($*));
}
alias calc='noglob calc'

calc 1./3
0.33333333333333331
calc 7.5 - 2.5
5.
share|improve this question
    
What about using zcalc (autoload zcalc; zcalc, info -f zsh --index-search=zcal)? –  Stéphane Chazelas Mar 2 '13 at 8:49
    
@StephaneChazelas: With zcalc, I get 0 for 1/3. Also, do you know a way to send the termination :q (or Ret keystroke) from the command line? I'd like to use it in the same way as the bc example above: e.g., calc "2*3" gets you 6 and the program terminates. –  Emanuel Berg Mar 3 '13 at 20:49
    
You can do 1./3 to force floating point arithmetics calc() echo $(($*)); alias 'calc=noglob calc'. Then: calc 2. / 3 * 5 –  Stéphane Chazelas Mar 3 '13 at 20:59
    
@StephaneChazelas: Wow! Made an edit. –  Emanuel Berg Mar 3 '13 at 21:22

4 Answers 4

up vote 3 down vote accepted

Using zsh's own arithmetic, you could do:

calc() printf '%.6g\n' $(($*))
alias 'calc=noglob calc'

But that would mean you'd need to enter numbers as 123. for them to be taken as floating point and trigger a floating point calculation.

You could work around that by appending . to any sequence of decimal digits that is not otherwise part of a hex number (or number in another base) or of a variable name or 12e-20 type numbers like:

setopt extendedglob
calc() printf '%.6g\n' $((${*//(#bm)(([0-9.]##[eE][-+][0-9]##|[[:alnum:]_#]#[.#_[:alpha:]][[:alnum:]_#]#)|([0-9]##))/$MATCH${match[3]:+.}}))
alias 'calc=noglob calc'

By which time you may think it easier to use bc and trim the trailing 0s.

See also awk:

calc() awk "BEGIN{print $*}"

which supports fewer operators and math functions but might be enough for you.

share|improve this answer
    
No, this is exactly what I want! It doesn't matter that it is complicated as I only have to put it in .zshrc once. And it is appealing to use the shell's calculator, rather than bc, although I never thought about that possibility when I first posted. Also, I have to say I'm surprised it took such a "hackish" solution --- one would think, presenting numerical data in a way pleasant for humans, that would be a tool almost from the infancy of computing! Anyway, this is great! –  Emanuel Berg Mar 4 '13 at 0:59
    
For any potential future users of this great hack/tool: multiplication isn't implicit on parenthesis, and, instead of ^, use **. When you know that, I guess it is not really a big thing, but if Mr. Chazelas finds joy in perfecting his creation, that would be a way to make it even more intuitive. For example, then you could get the utilization bound for 14 multi-CPUs with calc 14(2^(1/14) - 1). Am I disturbed, or wouldn't that be so cool? :) –  Emanuel Berg Aug 7 '13 at 3:02
    
@EmanuelBerg, well I'm quite used to ^ being XOR and ** being pow, but if you prefer ^ being pow, you can always add a ${ //\^/**} in there. –  Stéphane Chazelas Aug 7 '13 at 5:37
    
@EmanuelBerg, implicit multiplication conflicts with math function calls where variables may be use (cos(x) vs cos*(x)). –  Stéphane Chazelas Aug 7 '13 at 6:05
    
Aha! Get it. Well, most important, it is documented. –  Emanuel Berg Aug 8 '13 at 21:00

If I understood correctly, you want to delete trailing zeroes and a trailing dot. In that case, if EXTENDED_GLOB is set, you can use

${result//%.#0##/}

That is: at the end of the string (%) match zero or more dots (.#) followed by one or more zeroes (0##).

But this will return "" if result is 0. You can do another substitution around the first one, to restore the return value to 0:

${${result//%.#0##/}:-0}
share|improve this answer
    
Cool, will check this out bit by bit, might get back to you tomorrow. If you enjoyed this exercise, does it work for .3333 (etc.) to .33 as well? (And, .6666, etc.) :) But, if you feel you're done, removing zeroes and the dot is fine; I'll accept it as soon as I learned how you did it, and tested it. –  Emanuel Berg Mar 1 '13 at 23:47
    
Also (don't miss my first comment to your answer), you indicated it wasn't 100% clear what I intended, but now once you've got it, do you know how to put it in "math"? I actually studied math at a high level but I suppose I was a poor student because I don't know how to put it to precisely tell what I'm after, that's why I included all the examples. –  Emanuel Berg Mar 1 '13 at 23:51
    
I don't really get what you want to do with .3333. Just delete all decimal digits after the second? –  angus Mar 1 '13 at 23:55
    
Yes, that would be great! –  Emanuel Berg Mar 2 '13 at 0:50

bc can either print results as long integers or as decimals. Here's a script that joins long integers split into multiple lines together, and removes trailing zeroes after the decimal points in decimals.

calc () {
  emulate -L zsh; setopt extended_glob
  local line
  bc <<EOF |
scale=3
$@
EOF
    while read line; do
      if [[ $line = *.* ]]; then
        print -r -- ${${${line%%0##}/#%0#./0}%.}
      else
        print -r -- $line
      fi
    done
}

The way it's written is more of an exercise in text manipulation with parameter substitutions than a really clear way of pretty-printing decimals.

  • ${…%%0##} removes the longest suffix matching 0##, i.e. trailing zeroes.
  • ${…/#%0#./0} sets the string to 0 if it consists solely (#% prefix to the pattern in ${VAR/PATTERN/REPLACEMENT}) of optional leading zeroes (0#) and .
  • ${…%.} strips off a trailing . if any.

I think splitting the steps is clearer.

if [[ $line = *.* ]]; then line=${line%%0##}; fi
if [[ $line = . ]]; then line=0; else line=${line%.}
print -r -- $line
share|improve this answer

Try zcalc in zsh; if you're not already autoloading all the functions that come with zsh, you'll need to autoload zcalc first. Does away with bc, has prompting, $output back-references, command-history, scientific functions, the ability to define more functions, etc.

Documented in zshcontrib(1).

Downside: still has the "ints by default" issue, thus 3./5 != 3/5

share|improve this answer
    
Yes, check out this answer above. –  Emanuel Berg Mar 4 '13 at 23:44

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.