3

I am writing a simple bash script to calculate a square root to 3 decimal places by default but the user can set the number of places... Nothing complex, I just iterate from 1 to the lowest square root. Since my bash is still basic this is what I came up with.

#!/usr/bin/env bash

num=${1}
places=${2-3}
i=0

while [[ $(( i*i )) -lt ${num} ]]    // <= The problem should be here
do
    i=$(( i + 1 ))
done

echo ${i};
rem=$(( num % i ))
root="${i}."
for (( j=0; j<places; j++ ))
do
    rem=$((rem * 10))
    root="$root$((rem / i))"
    rem=$((rem % i))
done
echo ${root}

But due to some reason, it won't produce the correct result for a wide range of numbers like bashfile.sh 9 // will produce 3.000 but bashfile.sh 8 will return 3.666 kindly help, what is wrong with while [[ ]]

Aderemi Dayo
  • 705
  • 1
  • 11
  • 25

2 Answers2

3

The above code try to evaluate the sqrt with decimal in few steps:

  • Evaluate the integer part of N, store as I
  • Evaluate the decimal part of R=N-I*I, by dividing R by I.

However, this is incorrect. The code assumes that the breaks N into = (II) + (DI), however, the requirement is to find N = (D+I)(D+I) = DD + II + 2(D+I)

Two possible alternatives:

  • Multiple the number by 10^(PREC*2), take the sqrt as integer, then print the number with the last PREC digits after a decimal point.
  • Escape to to a tool capable of decimal numbers (awk, bc or dc)
  • Implement the decimal calculation as inary search

Implementing option #1:

PREC=3   # Precision
V=123456  # input
for ((i=1 ; i<=PREC ; i++ )) ; do
  V=V*100
done
# Loop for matching i as above
dash-o
  • 13,723
  • 1
  • 10
  • 37
2

Here is a fairly simple, although not very efficient solution:

#!/bin/bash

num=$1
prec=${2:-3}

for (( i=0; i < prec; ++i )); do let num*=100; done

for (( sqrt=0; sqrt*sqrt <= num; ++sqrt )); do :; done
let --sqrt

sqrt="${sqrt:0:-prec}.${sqrt: -prec}"
echo $sqrt

To answer your question ("what is wrong with while [[ ]]"), your comparison was slightly incorrect. Instead of -lt (ie, <) it should have been <=. And you needed to subtract 1 from i after the loop to get the correct whole part of the square root.

I couldn't make sense of the second part of your code that calculates decimal places. However, a simple solution is to instead pre-multiply the original number to get needed precision, which is what I've done above.


UPDATE

As @dash-o has pointed out if you are using bash, chances are you have either bc or awk installed, which are better suited for this:

num=$1
prec=${2:-3}

# using bc
echo "scale=$prec; sqrt($num)" | bc

# using awk
awk "BEGIN {printf \"%.${prec}f\n\", sqrt($num)}"
  • @AderemiDayo I noticed that too. It seems to be the 1st for-loop. If you are only using integers, you can try appending 0s instead of multiplication. – Super-intelligent Shade Oct 22 '19 at 15:08
  • It is the 2nd for-loop, if you supply precision of `8` it will loop `num*10000000000000000` times, I don't even know where to start calculating the big O for that... lol – Aderemi Dayo Oct 22 '19 at 15:15
  • @AderemiDayo oh you are right. In that case instead of ++ increment, maybe try using binary search. For simplicity, you can assume `num` to be the upper bound and then keep slicing it in half until you get the result. – Super-intelligent Shade Oct 22 '19 at 18:26