8

I would like to convert a number that is stored in scientific notation into a floating point decimal, so I can then perform some comparisons on the data. This is being done in a bash script - here is a small snippet of the code:

while read track_id landfall_num gate_id pres_inter
do
  if [[ $landfall_num == 0001 ]]
  then
     start_flag = true
     echo DING DING $start_flag
     if [[ $pres_inter < 97000 ]]
     then
        echo Strong Storm From North $track_id, $gate_id, $pres_inter
     fi
  fi
done < $file

My problem is that my < operand is selecting basically all of the pressure values, which are stored in scientific notation, when I use <, and none when I use >. I am looking at atmospheric pressure measurements in pascals rather than millibars.

Here is sample output:

Strong Storm From North 0039988 0017 1.0074E+05

Strong Storm From North 0037481 0018 9.9831E+04

Neither of these storms should be meeting the selection criteria!

johnsyweb
  • 136,902
  • 23
  • 188
  • 247
kimmyjo221
  • 685
  • 4
  • 10
  • 17

3 Answers3

8

First thing, bash cannot do arithmetic using floating point arithmetic. Second thing, bash doesn't know scientific notation (even for integers).

First thing you can try, if you're absolutely positively sure that all your numbers are integers: convert them to decimal notation: printf will happily do that for you:

printf -v pres_inter "%.f" "$pres_inter"

(%.f rounds to nearest integer).

then use bash's arithmetic:

if (( pres_inter < 97000 )); then ....

This solution is wonderful and doesn't use any external commands or any subshells: speed and efficiency!

Now if you're dealing with non integers you'd better use bc to do the arithmetic: but since bc is retarded and doesn't deal well with the scientific notation, you have to convert your numbers to decimal notation anyways. So something like the following should do:

printf -v pres_inter "%f" "$pres_inter"
if (( $(bc -l <<< "$pres_inter<97000") )); then ...

Of course, this is slower since you're forking a bc. If you're happy with integers, just stick to the first possibility I gave.

Done!

gniourf_gniourf
  • 44,650
  • 9
  • 93
  • 104
7

I think the best way to do it is using awk. For example, in the case of the number "1.0074E+05"

echo "1.0074E+05" | awk -F"E" 'BEGIN{OFMT="%10.10f"} {print $1 * (10 ^ $2)}'

the output:

100740.0000000000

obviously you can use less precision then 10 decimal :)

  • 1
    This should be the accepted answer. Short and sweet. Works for negatives too. Now I can have the price of SHIB in my terminal :) – Noah Gary Oct 13 '21 at 14:27
2

For numeric comparisons, you need to use:

  • -eq instead of ==
  • -ne instead of !=
  • -lt instead of <
  • -le instead of <=
  • -gt instead of >
  • -ge instead of >=

And to fix some syntax issues:

while read track_id landfall_num gate_id pres_inter
do
  landfall_num=$(printf "%f", "$landfall_num")
  if [[ "$landfall_num" -eq 1 ]]
  then
     start_flag="true"
     echo "DING DING $start_flag"
     if [[ "$pres_inter" -lt 97000 ]]
     then
        echo "Strong Storm From North $track_id, $gate_id, $pres_inter"
     fi
  fi
done < "$file"

Updated:

This is how you convert a scientific notation number into an integer:

printf "%.0f\n", 1.0062E+05

for example, prints:

100620

And so:

landfall_num=$(printf "%f", "$landfall_num")

will convert landfall_num from scientific notation into a normal decimal integer.

sampson-chen
  • 45,805
  • 12
  • 84
  • 81
  • thank you. Just curious - I just took a shell programming course last week and they didn't talk about all of the quotation marks. Why do you consider this better syntax? Thanks! – kimmyjo221 Dec 11 '12 at 18:46
  • Also, I am getting the following error: line 28: [[: 1.0062E+05: syntax error: invalid arithmetic operator (error token is ".0062E+05") – kimmyjo221 Dec 11 '12 at 18:48
  • It's almost always better to enclose variables in double quotes to eliminate corner cases such as empty strings, or strings that contain newlines. – sampson-chen Dec 11 '12 at 18:50
  • Don't use `[[` for arithmetic comparisons except when combined with some other type of test where it's cleaner to use a single command. Normally, use `(())` for arithmetic. Bash won't handle floats so neither approach will work anyway. – ormaaj Dec 11 '12 at 18:51
  • @kimmyjo221 that's because the sci-notation was never converted yet. bash itself doesn't support floating point comparisons, if integers are okay, see updated answer. – sampson-chen Dec 11 '12 at 18:52
  • integers would be fine! Could you describe the awk command I might use. Like I said - very new to shell scripting! Thanks :) – kimmyjo221 Dec 11 '12 at 18:54
  • It's not a problem of using `-lt` vs `<`, it's problem of scientific notations... Your line `if [[ "$pres_inter" -lt 97000 ]]` will fail. – gniourf_gniourf Dec 11 '12 at 19:01
  • And using a subshell to get the value of `printf` is retarded. Use its `-v` option... anyways, I already answered the question `:-p` – gniourf_gniourf Dec 11 '12 at 19:03
  • @gniourf_gniourf oh, neat; never used `prinf` before so I learned something new today =) – sampson-chen Dec 11 '12 at 19:07