11

I'm calculating aspect ratio height from x number, in this example I'm using 4:3 ratio, and a width of 800, the result (height) should be 600, but bash is returning 800, and I'm not sure why.

I've tried other languages, most seem to have issues too, php seems to be one of few that work.

PHP (returns 600)

php -r 'echo 800/(4/3);'

Python (returns 800)

python -c "print(800/(4/3))"

bc -l kinda works (returns 600.00000000000000000150)

-l is "Define the standard math library", not to sure what that means, but it seems to get me closer to my goal, but where is the extra 0's and 150 coming from?

echo '800 / (4 / 3)' | bc -l

I'm guessing it's something to do with floating point handling, or truncating the result of 3/4.

Now I could just use php, and call it a day, but seems kinda overkill for a relatively simple calculation. Any idea what's going on here.

8
  • 13
    BTW, Python 2 reached its End Of Life over 2 years ago. You really should consider upgrading to Python 3...
    – PM 2Ring
    Feb 1, 2022 at 11:15
  • 5
    Building on PM 2Ring's comment, only Python 2 returns 800 with that command. If you run python3 -c "print(800/(4/3))", it prints 600.0. Feb 1, 2022 at 21:22
  • 4
    Does this answer your question? How to do integer & float calculations, in bash or other languages/frameworks?
    – Kusalananda
    Feb 2, 2022 at 7:50
  • 1
    The 600.00000000000000000150 is a rounding error, similar to that (1/3)*3 is 0.9999999 and not 1. Feb 2, 2022 at 22:20
  • 1
    @Mint Depending on the tasks and colleagues around, there may be good reasons to adopt Python3 for anything newly written .and. to retain (some spare version) of Python2, too. You may instruct Linux to interpret python as call for python3, and still access the legacy with (then explicit) call python2. See this answer of mine on stackoverflow for an example.
    – Buttonwood
    Feb 3, 2022 at 15:20

2 Answers 2

56

Bash arithmetic is integer only. So 4/3 returns 1. And 800/1 is 800.

If you can control the inputs then you can re-factor and do the multiplication before the division

$ echo $(( 800*3/4 ))
600

Your other examples are also "integer". If, for example, you force python floating point by replace 4 with 4.0 then you get a different answer (Python 3 doesn't need this)

$ python -c "print(800/(4.0/3))"
600.0

bc -l loads the standard math library (with functions like s() for sine, l() for natural logarithm, etc), but more importantly here, sets scale to 20. scale defines how many decimals after the radix to generate in divisions, so 4/3 there will be 1.33333333333333333333 (in effect 133333333333333333333/1e+20), and that explains why you get 600.00000000000000000150.

echo 'scale=1000; 800/(4/3)' | bc

Will get you more precision (without having to load the math library), but you'll never get just 600 there as 4/3 cannot be represented in decimal.

10
  • 9
    @Mint 800*3/4 calculates first 800*3, then divides it by 4.
    – peterh
    Feb 1, 2022 at 1:59
  • 4
    Without any (...) it will do standard left-to-right evaluation; so 800*3=2400; 2400/4=600. In comparison 800*(3/4) would return 0 because the 3/4 would be 0, and 800*0 is 0. Feb 1, 2022 at 2:02
  • 2
    If one really want to muck with floats it in bash one can use printf with exponents / E-notation as in if one for example want 9 / 4 one can do printf '%f\n' "$(( 9 * 100 / 4 ))e-2" :P,
    – ibuprofen
    Feb 1, 2022 at 2:48
  • 1
    4/3 is an infinitely repeating decimal, so your result is always going to be dependent on how the language implementation handles floating point. Feb 1, 2022 at 15:17
  • 3
    @Mint Don't forget that computer math is not always the same as real math. See docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html and stackoverflow.com/questions/588004/…
    – Barmar
    Feb 1, 2022 at 15:46
1

Shell

In general, in shells, you need an external program to perform general math.

$ bc -l <<<'800/(4/3)'
$ bc -l <<<'scale=200;800/(4/3)'
$ awk 'BEGIN{print 800/(4/3)}'
$ awk 'BEGIN{printf "%.60f\n",800/(4/3)}'
$ python3 -c 'print(800/(4/3))'
$ python3 -c 'print(format(800/(4/3),".60f"))'

Why

It's a common result of two issues:

  • Individual operation precision.
  • Order of operations.

Individual precision

If the operations are carried out as integer, the value of (4/3) is 1.

Even in Python (well, Python 2 as the / means "Integer Division" there):

$ python2 -c 'print(4/3)'
1

But not in Python3:

$ python3 -c 'print(4/3)'
1.3333333333333333

That is why a 800/(4/3) becomes 800/1 and result in 800. (in Python2)

$ python2 -c 'print(800/(4/3))'
800

$ python3 -c 'print(800/(4/3))'
600.0

Bash (as most shells) is similar to python2 (integer):

$ bash -c 'echo (800/(4/3))'
800

Order

You can re-order the math to avoid the integer conversion problem.

$ python2 -c 'print(800*3/4)'
600

Or tell python to use floats:

$ python2 -c 'print(800/(float(4)/3))'
600.0

Limit

But don't fall into the illusion that such number is exact. It certainly may look like that:

python2 -c 'print(format(800/(4.0/3),".80f"))'
600.00000000000000000000000000000000000000000000000000000000000000000000000000000000

And a 2^-50 (or 55) value might be exactly represented in binary:

$ python2 -c 'print(format(2**-50,".80f"))'
0.00000000000000088817841970012523233890533447265625000000000000000000000000000000

$ python2 -c 'print(format(2**-55,".80f"))'
0.00000000000000002775557561562891351059079170227050781250000000000000000000000000

But as soon as you mix integers and floats (or do general numeric math) you are bound to get "out of limits" results:

$ python2 -c 'print(format(1 + 2**-50,".80f"))'
1.00000000000000088817841970012523233890533447265625000000000000000000000000000000

$ python2 -c 'print(format(1 + 2**-55,".80f"))'
1.00000000000000000000000000000000000000000000000000000000000000000000000000000000

In general: Numbers with more than 53 binary digits get truncated in double precision floats.

0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .