Differences between revisions 73 and 84 (spanning 11 versions)
Revision 73 as of 2012-11-12 19:11:23
Size: 4370
Editor: 95
Comment: Sorry, "bc" worked in its particular way. I revert the latest changes that I made. Thanks for the Bash FAQ!
Revision 84 as of 2021-09-01 06:31:58
Size: 5420
Editor: geirha
Comment: replacing echo with printf
Deletions are marked like this. Additions are marked like this.
Line 2: Line 2:
Line 5: Line 4:
Line 7: Line 5:
$ echo $((10/3)) $ printf '%s\n' "$((10 / 3))"
Line 10: Line 8:
For most operations involving floating-point numbers, an external program must be used, e.g. `bc`, [[AWK]] or `dc`:
Line 12: Line 9:
For most operations involving non-integer numbers, an external program must be used, e.g. `bc`, [[AWK]] or `dc`:
Line 13: Line 11:
$ echo "scale=3; 10/3" | bc $ printf 'scale=3; 10/3\n' | bc
Line 16: Line 14:
Line 18: Line 17:
Same example with `dc` (reversed polish calculator, lighter than `bc`):
Same example with `dc` (reverse polish calculator, lighter than `bc`):
Line 21: Line 19:
$ echo "3 k 10 3 / p" | dc $ printf '3 k 10 3 / p\n' | dc
Line 24: Line 22:
Line 26: Line 25:
If you are trying to compare floating point numbers (less-than or greater-than), and you have GNU `bc`, you can do this:
If you are trying to compare non-integer numbers (less-than or greater-than), and you have GNU `bc`, you can do this:
Line 29: Line 27:
# Bash
if (( $(bc <<< "1.4 < 2.5") )); then
  echo "1.4 is less than 2.5."
# Bash and GNU bc
if (( $(bc <<<'1.4 < 2.5') )); then
  printf '1.4 is less than 2.5.\n'
Line 34: Line 32:
Line 35: Line 34:
Line 37: Line 35:
# This would work with some versions, but not HP-UX 10.20.
imadev:~$ bc <<< '1 < 2'
# HP-UX 10.20.
imadev:~$ bc <<<'1 < 2'
Line 41: Line 39:
Line 42: Line 41:
Line 45: Line 43:
case $(echo "1.4 - 2.5" | bc) in
  -*) echo "1.4 is less than 2.5";;
case $(printf '%s - %s\n' 1.4 2.5 | bc) in
  -*) printf '1.4 is less than 2.5\n' ;;
Line 49: Line 47:
Line 52: Line 51:
Line 59: Line 57:
Line 60: Line 59:
Line 65: Line 63:
Line 67: Line 66:
Newer versions of zsh and the KornShell have built-in floating point arithmetic, together with mathematical functions like {{{sin()}}} or {{{cos()}}}. So many of these calculations can be done natively in ksh:
ksh93, zsh and yash have support for non-integers in shell arithmetic. zsh (in the `zsh/mathfunc` module) and ksh93 additionally have support for some C99 math.h functions {{{sin()}}} or {{{cos()}}} as well as user-defined math functions callable using C syntax. So many of these calculations can be done natively in ksh or zsh:
Line 70: Line 68:
# ksh93
$ echo $((3.00000000000/7))
# ksh93/zsh/yash
$ LC_NUMERIC=C; printf '%s\n' "$((3.00000000000/7))"
Line 74: Line 72:
Comparing two floating-point numbers for ''equality'' is actually an unwise thing to do; two calculations that should give the same result on paper may give ever-so-slightly-different floating-point numeric results due to rounding/truncation issues. If you wish to determine whether two floating-point numbers are "the same", you may either:
(ksh93 and yash are sensitive to locale. In ksh93, a dotted decimal used in locales where the decimal separator character is not ''dot'' will fail, like in German, Spanish, French locales... In yash, the locale's decimal radix is only honoured in the result of arithmetic expansions.).

Comparing two non-integer numbers for ''equality'' is potentially an unwise thing to do. Similar calculations that are mathematically equivalent and which you would expect to give the same result on paper may give ever-so-slightly-different non-integer numeric results due to rounding/truncation and other issues. If you wish to determine whether two non-integer numbers are "the same", you may either:
Line 78: Line 79:
 * Be sure to output adequate precision to fully express the actual value. Ideally, use [[http://www.exploringbinary.com/hexadecimal-floating-point-constants/|hex float literals]], which are supported by Bash.
Line 79: Line 81:
One of the very few things that Bash actually ''can'' do with floating-point numbers is round them, using `printf`: {{{
 $ ksh93 -c 'LC_NUMERIC=C printf "%-20s %f %.20f %a\n" "error accumulation:" .1+.1+.1+.1+.1+.1+.1+.1+.1+.1{,,} constant: 1.0{,,}'
error accumulation: 1.000000 1.00000000000000000011 0x1.0000000000000002000000000000p+0
constant: 1.000000 1.00000000000000000000 0x1.0000000000000000000000000000p+0
}}}
Line 81: Line 87:
One of the very few things that Bash actually ''can'' do with non-integer numbers is round them, using `printf`:
Line 86: Line 93:
printf -v a1 %.2f $a
printf -v b1 %.2f $b
if [[ $a1 = "$b1" ]]; then echo "a and b are the same, roughly"; fi
printf -v a1 %.2f "$a"
printf -v b1 %.2f "$b"
if [[ $a1 = "$b1" ]]; then
  printf 'a and b are roughly the same\n'
fi
Line 90: Line 99:
Caveat: Many problems that look like floating point arithmetic can in fact be solved using integers only, and thus do not require these tools -- e.g., problems dealing with rational numbers. For example, to check whether two numbers {{{x}}} and {{{y}}} are in a ratio of 4:3 or 16:9 you may use something along these lines:
Line 92: Line 100:
Many problems that look like non-integer arithmetic can in fact be solved using integers only, and thus do not require these tools -- e.g., problems dealing with rational numbers. For example, to check whether two numbers {{{x}}} and {{{y}}} are in a ratio of 4:3 or 16:9 you may use something along these lines:
Line 95: Line 104:
if (( $x*9-$y*16==0 )) ; then
   echo "16:9."
elif (( $x*3-$y*4==0 )) ; then
   echo "4:3."
if (( (x * 9 - y * 16) == 0 )) ; then
   printf '16:9.\n'
elif (( (x * 3 - y * 4) == 0 )) ; then
   printf '4:3.\n'
Line 100: Line 109:
   echo "Neither 16:9 nor 4:3."    printf 'Neither 16:9 nor 4:3.\n'
Line 103: Line 112:
A more elaborate test could tell if the ratio is closest to 4:3 or 16:9 without using floating point arithmetic. Note that this very simple example that apparently involves floating point numbers and division is solved with integers and no division. If possible, it's usually more efficient to convert your problem to integer arithmetic than to use floating point arithmetic.
A more elaborate test could tell if the ratio is closest to 4:3 or 16:9 without using non-integer arithmetic. Note that this very simple example that apparently involves non-integer numbers and division is solved with integers and no division. If possible, it's usually more efficient to convert your problem to integer arithmetic than to use non-integer arithmetic.

How can I calculate with floating point numbers instead of just integers?

BASH's builtin arithmetic uses integers only:

$ printf '%s\n' "$((10 / 3))"
3

For most operations involving non-integer numbers, an external program must be used, e.g. bc, AWK or dc:

$ printf 'scale=3; 10/3\n' | bc
3.333

The "scale=3" command notifies bc that three digits of precision after the decimal point are required.

Same example with dc (reverse polish calculator, lighter than bc):

$ printf '3 k 10 3 / p\n' | dc
3.333

k sets the precision to 3, and p prints the value of the top of the stack with a newline. The stack is not altered, though.

If you are trying to compare non-integer numbers (less-than or greater-than), and you have GNU bc, you can do this:

# Bash and GNU bc
if (( $(bc <<<'1.4 < 2.5') )); then
  printf '1.4 is less than 2.5.\n'
fi

However, x < y is not supported by all versions of bc:

# HP-UX 10.20.
imadev:~$ bc <<<'1 < 2'
syntax error on line 1,

If you want to be portable, you need something more subtle:

# POSIX
case $(printf '%s - %s\n' 1.4 2.5 | bc) in
  -*) printf '1.4 is less than 2.5\n' ;;
esac

This example subtracts 2.5 from 1.4, and checks the sign of the result. If it is negative, the first number is less than the second. We aren't actually treating bc's output as a number; we're treating it as a string, and only looking at the first character.

Legacy (Bourne) version:

# Bourne
case "`echo "1.4 - 2.5" | bc`" in
  -*) echo "1.4 is less than 2.5";;
esac

AWK can be used for calculations, too:

$ awk 'BEGIN {printf "%.3f\n", 10 / 3}'
3.333

There is a subtle but important difference between the bc and the awk solution here: bc reads commands and expressions from standard input. awk on the other hand evaluates the expression as part of the program. Expressions on standard input are not evaluated, i.e. echo 10/3 | awk '{print $0}' will print 10/3 instead of the evaluated result of the expression.

ksh93, zsh and yash have support for non-integers in shell arithmetic. zsh (in the zsh/mathfunc module) and ksh93 additionally have support for some C99 math.h functions sin() or cos() as well as user-defined math functions callable using C syntax. So many of these calculations can be done natively in ksh or zsh:

# ksh93/zsh/yash
$ LC_NUMERIC=C; printf '%s\n' "$((3.00000000000/7))"
0.428571428571428571

(ksh93 and yash are sensitive to locale. In ksh93, a dotted decimal used in locales where the decimal separator character is not dot will fail, like in German, Spanish, French locales... In yash, the locale's decimal radix is only honoured in the result of arithmetic expansions.).

Comparing two non-integer numbers for equality is potentially an unwise thing to do. Similar calculations that are mathematically equivalent and which you would expect to give the same result on paper may give ever-so-slightly-different non-integer numeric results due to rounding/truncation and other issues. If you wish to determine whether two non-integer numbers are "the same", you may either:

  • Round them both to a desired level of precision, and then compare the rounded results for equality; or
  • Subtract one from the other and compare the absolute value of the difference against an epsilon value of your choice.

  • Be sure to output adequate precision to fully express the actual value. Ideally, use hex float literals, which are supported by Bash.

 $ ksh93 -c 'LC_NUMERIC=C printf "%-20s %f %.20f %a\n" "error accumulation:" .1+.1+.1+.1+.1+.1+.1+.1+.1+.1{,,} constant: 1.0{,,}'
error accumulation:  1.000000 1.00000000000000000011 0x1.0000000000000002000000000000p+0
constant:            1.000000 1.00000000000000000000 0x1.0000000000000000000000000000p+0

One of the very few things that Bash actually can do with non-integer numbers is round them, using printf:

# Bash 3.1
# See if a and b are close to each other.
# Round each one to two decimal places and compare results as strings.
a=3.002 b=2.998
printf -v a1 %.2f "$a"
printf -v b1 %.2f "$b"
if [[ $a1 = "$b1" ]]; then
    printf 'a and b are roughly the same\n'
fi

Many problems that look like non-integer arithmetic can in fact be solved using integers only, and thus do not require these tools -- e.g., problems dealing with rational numbers. For example, to check whether two numbers x and y are in a ratio of 4:3 or 16:9 you may use something along these lines:

# Bash
# Variables x and y are integers
if (( (x * 9 - y * 16) == 0 )) ; then
   printf '16:9.\n'
elif (( (x * 3 - y * 4) == 0 )) ; then
   printf '4:3.\n'
else
   printf 'Neither 16:9 nor 4:3.\n'
fi

A more elaborate test could tell if the ratio is closest to 4:3 or 16:9 without using non-integer arithmetic. Note that this very simple example that apparently involves non-integer numbers and division is solved with integers and no division. If possible, it's usually more efficient to convert your problem to integer arithmetic than to use non-integer arithmetic.


CategoryShell

BashFAQ/022 (last edited 2021-09-01 06:31:58 by geirha)