# Why doesn't awk make the sum zero but instead a very small number?

I have this file and I want to sum all the number in first column. Easy:

``````awk '{s+=\$1;print \$1,s}' file
0.1048 -1.2705
0.4196 -0.8509
0.4196 -0.4313
0.2719 -0.1594
0.0797 -0.0797
0.0797 -5.55112e-17   #Notice this line
``````

You see, the last one should be 0. I know that `e-17` is zero, but sometimes the output is exactly 0. If it's not 0, the output is in range of `e-15` to `e-17`, in negative or positive sign. To fix this, I have to use the absolute value:

``````awk '{s+=\$1;if (sqrt(s^2)<0.01) s=0;print \$1,s}' file
``````

Do you know why this happens?

Your question is “Why does this happen?”, but your implicit question (which others have addressed) is “How can I fix this?”  You figured out an approach, which you raised in a comment:

So if I multiply it to 1000 to eliminate the point, I can get the exact result, can’t I?

Yes.  Well, 10000, since you have four decimal places.  Consider this:

``````awk '{ s+=\$1*10000; print \$1, s/10000 }'
``````

Unfortunately, this doesn’t work, because the corruption has already occurred as soon as we interpret the token (string) as a decimal number.  For example, `printf "%.20f\n"` shows that the input data `0.4157` is actually interpreted as 0.41570000000000001394.  In this case, multiplying by 10000 gets you what you would expect: 4157.  But, for example, `0.5973` = 0.59730000000000005311, and multiplying that by 10000 yields 5973.00000000000090949470.

``````awk '{ s+=int(\$1*10000); print \$1, s/10000 }'
``````

to convert the numbers that “should be” whole numbers (e.g., 5973.00000000000090949470) into the corresponding whole numbers (5973).  But that fails because sometimes the conversion error is negative; e.g., `0.7130` is 0.71299999999999996714.  And `awk`’s `int(expr)` functions truncates (toward zero) rather than rounding, so `int(7129.99999999)` is 7129.

So, when life gives you lemons, you make lemonade.  And when a tool gives you a truncate function, you round by adding 0.5.  7129.99999999+0.5≈7130.49999999, and, of course, `int(7130.49999999)` is 7130.  But remember: `int()` truncates toward zero, and your input includes negative numbers.  If you want to round –7129.99999999 to –7130, you need to subtract 0.5 to get –7130.49999999.  So,

``````awk '{ s+=int(\$1*10000+(\$1>0?0.5:-0.5)); print \$1, s/10000 }'
``````

which adds –0.5 to `\$1*10000` if `\$1` is ≤ 0.

• Nice explanation and solution. But I don't understand what's wrong when you take 0.5973 times 10000. Can you give me more detail? Thank you. – Ooker Jul 9 '14 at 23:19
• I’m not sure what you’re asking. (1) What’s happening? I’m not sure, exactly. What nobody else has said is that people typically use decimal (base 10) to represent numbers, but computers use binary (base 2). So, when a computer reads a decimal number, it converts it to binary, and then converts back to decimal on output. For example, ½ is 0.5 in decimal, 0.1 in binary; ¾ is 0.75 in decimal, 0.11 in binary. And some simple fractions cannot be represented exactly in decimal; e.g., ⅓ is 0.33333333…. When you truncate to a finite number of digits, you get an approximation (i.e., rounding error). … – Scott Jul 9 '14 at 23:51
• … Well, the only fractions that can be represented exactly in binary are fractions of the form (some integer) ∕ (a power of 2), so, while a simple fraction like ⅕ can be represented exactly in decimal (as 0.2), it cannot be represented exactly in binary. Now, it happens to work out that some decimal numbers of the form 0.nnnn, when converted to binary at a certain precision (number of bits) and multiplied by 10000 at that same precision, come out to the original nnnn. And some don’t. I can’t explain why. … – Scott Jul 9 '14 at 23:52
• … But I guess you might also be asking (2) why does it matter? It’s because we’re trying to get the exact results that you want. So if we have the input `0.5973`, `-0.5971`, and `-0.0002`, and we multiply each (converted to binary) number by 10000, we get 5973.00000000000090949470, -5971, and -2. So the sum is 0.00000000000090949470. And that’s why we need to round the numbers to the nearest integer after we’ve multiplied by 10000. – Scott Jul 9 '14 at 23:53
• Why does only 0.5973 become 5973.000000 but not also -0.5971 and -0.0002 when we multiply them with 10000? Does that always happen or it's just an example? Also, I think we should round the numbers before we divide them back, not after they are multiplied by 10000 because adding 0.5 many times can be dangerous. – Ooker Jul 10 '14 at 0:27

It happens because a computer only has a limited precision when dealing with numbers. And the available precision uses a binary format to represent the number.

This makes numbers that appear to be trivial to write in our decimal system only representable as an approximation (see the Wikipedia entry on this): e.g. `0.1` (as in `1/10`) is really stored as something like `0.100000001490116119384765625` on the computer.

So all your numbers are really only handled by an approximation (unless you are lucky and have numbers like `0.5` that can be represented exactly).

Summing up all those approximate numbers can eventually lead to an error that is `!= 0`.

• So you mean that awk (or unix system) can't handle four digits after the point? Is that normal? In the wiki entry, they can handle to the sixth digit. – Ooker Jul 9 '14 at 10:38
• i'm saying that no IEEE754 32bit floating point number can represent certain numbers exactly (regardless whether this is unix, or w32 or whatever). you will always get an error (and the error you have is `about 10^-15`, this is the 15th digit, not just the sixth). the reason why you don't often see this, is because many programs do some rounding in the output. – umläute Jul 9 '14 at 11:04
• I see. But why when the result is `1` (which is exactly `1.00000xyz`), the program do rounding it, but not do so with `0.000xyz`? – Ooker Jul 9 '14 at 11:15
• because `print` also does some rounding in order to save characters :-) now if your number is `0.000000000000000000052` you can easily print this as `5.2e-20` (takes 7 characters, rather than 24), wheras `1.000000000000000000052` is rather complicated to print (you could do `1+5.2e-20` but that is not the usual scientific notation) – umläute Jul 9 '14 at 11:22
• @umläute: You say “… the error you have is … [in] the 15th digit, not just the sixth.” I suspect that Ooker can count. He’s saying that he would understand an approximation error for 0.1428571 or 0.12345678, but he’s questioning why 0.142857 or 0.4157 would have a problem. – Scott Jul 9 '14 at 21:09

As a way around this, you could use a program that is specifically designed to handle arithmetic operations like `bc`:

``````\$ awk '{printf "%s + ",\$1}' file | sed 's/\+ \$/\n/' | bc
0
``````

If, as seems to be the case, you have a fixed number of decimal places, you could simply remove them to work with integers and then add them again at the end:

``````\$ awk '{sub("0.","",\$1);s+=\$1;}END{print s/10000}' file
0
``````

or

``````\$ perl -lne 's/0\.//; \$s+=\$_; END{print \$s/10000}' file
0
``````
• If I have a complex awk with `if`, I think that using its built-in calculation is better. – Ooker Jul 9 '14 at 11:23
• @Ooker depends on how important precision is. As you pointed out, you can have issues when calculating floating point numbers. – terdon Jul 9 '14 at 11:25
• If you built an abs function, you won't need to pipe. – Ooker Jul 9 '14 at 11:30
• @Ooker see updated answer for another approach. – terdon Jul 9 '14 at 11:59
• Nice one :D. Just to make sure, will this approach be good when your number is not start with 0, but other digit? I think there will be no problem. – Ooker Jul 9 '14 at 14:13

Most versions of `awk` have a `printf` command. Instead of

``````print \$1,s
``````

use

``````printf "%.4f %.4f\n",\$1,s
``````

and the outputs will be rounded to 4 decimal places. That way you won't see most rounding errors.

• Nice. But the problem only appears when the number is `0.xyz`. Substitution by '0' is easier to understand and makes the `print` neater. – Ooker Jul 9 '14 at 14:44

This is not `awk` unique problem, it's also another programming languages problems. Example with `perl`:

``````\$ perl -anle '\$sum+=\$F}{print \$sum' file
-5.55111512312578e-17
``````

It's the problem of representing a non-terminating series for base 2 using a finite number of binary digits. Floating point numbers are not integers. It can take an infinitive amount of memory to store floating point numbers.

• Wow, you're right! I had never seen that before. So the `}{` acts like a `{}END{}`? – terdon Jul 9 '14 at 11:20
• @terdon: yes, because `-n` cause you already in a `while` loop. So it's really `while{...}{...}` – cuonglm Jul 9 '14 at 11:21
• That I understood, it was the lone `}{` instead of `{}{}` that confused me. – terdon Jul 9 '14 at 11:25