$ echo 0.4970436865354813 | awk -v CONVFMT=%.17g '{gsub($1, $1*1.1)}; {print}'
0.54674805518902947
Or rather here:
$ echo 0.4970436865354813 | awk '{printf "%.17g\n", $1*1.1}'
0.54674805518902947
is probably the best you can achieve. Use bc instead for arbitrary precision.
$ echo '0.4970436865354813 * 1.1' | bc -l
.54674805518902943
Answer from Stéphane Chazelas on Stack Exchange$ echo 0.4970436865354813 | awk -v CONVFMT=%.17g '{gsub($1, $1*1.1)}; {print}'
0.54674805518902947
Or rather here:
$ echo 0.4970436865354813 | awk '{printf "%.17g\n", $1*1.1}'
0.54674805518902947
is probably the best you can achieve. Use bc instead for arbitrary precision.
$ echo '0.4970436865354813 * 1.1' | bc -l
.54674805518902943
For higher precision with (GNU) awk (with bignum compiled in) use:
$ echo '0.4970436865354813' | awk -M -v PREC=100 '{printf("%.18f\n", $1)}'
0.497043686535481300
The PREC=100 means 100 bits instead of the default 53 bits.
If that awk is not available, use bc
$ echo '0.4970436865354813*1.1' | bc -l
.54674805518902943
Or you will need to learn to live with the inherent imprecision of floats.
In your original lines there are several issues:
- A factor of 1.1 is 10% increase, not 1% (should be a 1.01 multiplier). I'll use 10%.
The conversion format from a string to a (floating) number is given by CONVFMT. Its default value is
%.6g. That limits the values to 6 decimal digits (after the dot). That is applied to the result of the gsub change of$1.$ a='0.4970436865354813' $ echo "$a" | awk '{printf("%.16f\n", $1*1.1)}' 0.5467480551890295 $ echo "$a" | awk '{gsub(1*1.1)}; {printf("%.16f\n", $1)}' 0.5467480000000000
The printf format
gremoves trailing zeros:$ echo "$a" | awk '{gsub(1*1.1)}; {printf("%.16g\n", $1)}' 0.546748 $ echo "$a" | awk '{gsub(
1*1.1)}; {printf("%.17g\n", $1)}' 0.54674800000000001
Both issues could be solved with:
$ echo "$a" | awk '{printf("%.17g\n", $1*1.1)}' 0.54674805518902947Or
$ echo "$a" | awk -v CONVFMT=%.30g '{gsub(1*1.1)}; {printf("%.17f\n", $1)}' 0.54674805518902947
But don't get the idea that this means higher precision. The internal number representation is still a float in double size. That means 53 bits of precision and with that you could only be sure of 15 correct decimal digits, even if many times up to 17 digits look correct. That's a mirage.
$ echo "$a" | awk -v CONVFMT=%.30g '{gsub(
1*1.1}; {printf("%.30f\n", $1)}'
0.546748055189029469325134868996
The correct value is:
$ echo "scale=18; 0.4970436865354813 * 1.1" | bc
.54674805518902943
Which could be also calculated with (GNU) awk if the bignum library has been compiled in:
$ echo "$a" | awk -M -v PREC=100 -v CONVFMT=%.30g '{printf("%.30f\n", $1)}'
0.497043686535481300000000000000
floating point calculations in awk - Stack Overflow
How To Work With Large Floating Point Numbers In AWK?
shell - How to print float numbers with awk - Stack Overflow
floating point arithmetic in the shell: awk or bc?
you can control the number of decimal points with printf, eventually though the numbers won't be significant due to floating point representation
for example
awk 'BEGIN{for(i=5;i<20;i++) printf "%."i"f\n", 1./3}'
0.33333
0.333333
0.3333333
0.33333333
0.333333333
0.3333333333
0.33333333333
0.333333333333
0.3333333333333
0.33333333333333
0.333333333333333
0.3333333333333333
0.33333333333333331
0.333333333333333315
0.3333333333333333148
To complement karakfa's helpful answer:
In addition to explicit number formatting with printf and sprintf, you can also change awk's default floating-point number formatting, via built-in variable OFMT.
Note that it does not apply to integers.
OFMT defaults to %.6g, which means that any floating-point number is rounded to 6 significant digits and for exponents starting with 7 is represented in scientific notation.
Calculation result 1887436.8 - which has 8 significant digits - is therefore represented as 1.88744e+06, i.e., in scientific notation with 6 significant digits.
The following example sets OFMT to %1.f in order to output all floating-point numbers with 1 decimal place by default:
$ awk -v OFMT='%.1f' 'BEGIN {ram=(1.8 * 1024) * 1024; print ram}'
1887436.8
Note, however, that OFMT does not apply in the following scenarios:
If the floating-point number is used in a string concatenation:
$ awk -v OFMT='%.1f' 'BEGIN { print "result: " 1 / 3 }' result: 0.333333 # Workaround: Use `sprintf()` with OFMT awk -v OFMT='%.1f' 'BEGIN { print "result: " sprintf(OFMT, 1 / 3) }' result: 0.3If a literal can be parsed as an integer - even if it looks like a floating-point number:
$ awk -v OFMT='%.1f' 'BEGIN { print 1.000 }' 1
Caveat: There are many subtleties around number conversion and formatting in awk, not least because of the limited precision of floating-point numbers (which in awk are always of the ISO C double type).
I've noticed that in AWK, whenever you deal with numbers with a lot of digits and a decimal value.....it will round it off or sometimes even just turn it into an integer.
I don't notice this happening with smaller decimal numbers.
Is there a way around this? Maybe a tool? I know you can just multiply the numbers by powers of 10 to temporarily turn it into an integer, and then turn it back into decimal........but the problem with that is that if the numbers get really big, they turn into scientific numbers.
So is there any way to just keep them the way they are and deal with them?
I know of the BC tool in bash (which lets you work with floating point numbers because bash is designed to handle integers). But I don't know how to do this in AWK.
You're in a locale where the decimal separator is not . so the . is being treated as any other character that's not part of a number (e.g. a or @) and so your numbers are being truncated to the part before the ..
Do LC_ALL=C awk '...' to set your locale to one that does use a . for the decimal separator and then your script will work as-is.
I think you are closer than you think, but your problems stem from not handling putting the pieces together in the right order. By default awk handles floating-point math. Your summation of sum[$1] += $2 will be done with floating point numbers if your input numbers contains decimal places.
Why you are using a Command Substitution, e.g. $(..) is unclear. That isn't needed to fill temp.txt with the sorted sums from your input. Let's take for example your input file in a file named input. Then to sum and output the values you need nothing more than:
awk '{sum[
2} END {for (i in sum) print i, sum[i]}' input
Which results in the output of:
Bruce 83.1
Dave 42.7
Adrian 51
Janick 65.4
To sort the results, simply pipe the awk output to sort, e.g.
awk '{sum[
2} END {for (i in sum) print i, sum[i]}' input | sort
Result:
Adrian 51
Bruce 83.1
Dave 42.7
Janick 65.4
Now, to fill temp.txt, you simply need to redirect the output to temp.txt, e.g.
awk '{sum[
2} END {for (i in sum) print i, sum[i]}' input | sort > temp.txt
temp.txt now contains the sorted output. To nicely tabularize the output, you can format the output by saving the max length for $1 and setting the name field-width to max and then outputting the floating-point number in a fixed format (total floating-point field-width 10 with 1 digit following the decimal point), e.g.
{sum[$1] += $2; len=length($1); if(len>max)max=len} ... printf "%-*s%10.1f\n", max, i, sum[i]
Or in full-form:
awk '{sum[
2; len=length($1); if(len>max)max=len} END {for (i in sum) printf "%-*s%10.1f\n", max, i, sum[i]}' input | sort > temp.txt
With the results in temp.txt now being:
$ cat temp.txt
Adrian 51.0
Bruce 83.1
Dave 42.7
Janick 65.4
Dear all,
I am writing again to seek your help regarding POSIX shell scripting.
Since the standard does not define FP arithmetic directly in the shell, we need an external tool to compute. The two primary candidates are awk and bc/dc.
If you have experience with FP in the shell, which do you think is more appropriate, or if there are different situations for each, what are the main difference? When does it make sense to user one over the other?
Also, if I have missed another POSIX utility that can be used, let me know!
Thank you for your time!
awk's printf, like C's, lets you use a * instead of a hardcoded width or precision number. The value is instead taken from the next argument to the function. So instead of messing with shell substitution into the awk script, you can just pass the precision as another variable:
$ result=2050
$ prec=2
$ awk -v res="$result" -v p="$prec" 'BEGIN { printf "%.*f\n", p, res/1000 }'
2.05
However, if you're going to do a lot with floating point numbers, I'd suggest moving away from bash to another shell like zsh or ksh that supports floating-point math natively, or to a non-shell scripting language (perl, tcl, ruby, etc.). Tends to be more efficient than having to run external programs to do basic things like division.
I suggest:
result="123456"
var=2 # decimal places
awk -v res="$result" -v dp="$var" 'BEGIN {printf "%." dp "f", res / 1000}'
or
awk -v res="$result" -v CONVFMT="%.${var}f" 'BEGIN {printf res / 1000}'
Output:
123.46
From man awk:
CONVFMT: The conversion format for numbers, "%.6g", by default.