- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
I'm running below code in SAS to subtract few numbers :
data _null_;
res=301.73 - 60.35 - 0 - 0 - 241.38;
put res=;
run;
the expected value of res variable should be 0 but I am getting "2.842171E-14".
Please suggest ways to resolve this. Thanks in advance.
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
Hello @shashank12 and welcome to the SAS Support Communities!
Computers tend to produce tiny rounding errors in calculations involving non-integer numbers (see Numerical Accuracy in SAS Software for the details or many examples on this forum).
In most cases you can avoid such issues by using the ROUND function with an appropriate small rounding unit:
res=round(301.73 - 60.35 - 0 - 0 - 241.38, 1e-9);
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
Thanks a lot @FreelanceReinh
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
@FreelanceReinh I wanted to ask whether 1e-9 is small enough to perform all kinds of subtractions. As this subtraction is dynamic and I might get different numbers.
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
@shashank12 wrote:
@FreelanceReinh I wanted to ask whether 1e-9 is small enough to perform all kinds of subtractions. As this subtraction is dynamic and I might get different numbers.
The rounding unit must be chosen small enough not to influence results, but big enough to correct rounding errors in the least significant bits. The value of 1E-9 is suitable for a wide range of practical applications, but -- obviously -- if you subtract very small numbers (like millionths), 1E-9 could be too large. Similarly, if you subtract very large numbers (e.g. in the millions, but with one or two decimal places) 1E-9 could be too small. Also, the more numbers (with decimals) are involved in the calculation, the larger the rounding errors can become.
If you have always only up to two decimal places (like dollars and cents), you can circumvent the issues with numeric representation error by multiplying all numbers by 100 and then perform the subtraction with integers. Note, however, that even multiplying by 100 can be prone to those tiny rounding errors, so proper rounding (here: simply to integers) is important:
40 data _null_; 41 if 0.55*100 ne 55 then put 'Surprised?'; 42 if round(0.55*100)=55 then put 'OK!'; 43 run; Surprised? OK! NOTE: DATA statement used (Total process time):
Edit: Or, of course, just use 0.01 as the rounding unit in this situation.
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
As a remedy, use the ROUND function to get rid of the artifacts.
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
Thanks a lot @Kurt_Bremser
- Mark as New
- Bookmark
- Subscribe
- Mute
- RSS Feed
- Permalink
- Report Inappropriate Content
@shashank12 wrote:
(...)
res=301.73 - 60.35 - 0 - 0 - 241.38;
the expected value of res variable should be 0 but I am getting "2.842171E-14".
These "2.842171E-14" are really 2**-45 (=2.8421709430404007434844970703125E-14).
Here's how SAS obtains this seemingly wrong result:
First, it computes the difference 301.73 - 60.35. Sadly, 96% (!) of the numbers with up to two decimal places have infinitely many digits in the binary system, so rounding them to a binary floating-point number with finite precision incurs a rounding error (numeric representation error). Your three non-zero numbers are not among the 4% exceptions. For example, 301.73 written in the binary system,
100101101.1011101011100001010001 11101011100001010001 11101011100001010001...
is a periodic fraction with the 20-digit pattern "11101011100001010001" repeated infinitely often. On Windows and Unix platforms the 53rd binary digit -- highlighted above in green -- would be the last that could fit into the internal 64-bit representation. The subsequent digits "101011100001010001..." are rounded up. So, the resulting binary number in the computer's memory
100101101.10111010111000010100011110101110000101001000
is already slightly too large (by 1.28 * 2**-46 = 1.8189...E-14), i.e., the computer sees what we (humans) would write in the decimal system as
301.73000000000001818989403545856475830078125
which is where the problems with your calculation start. (Similarly, the 60.35 is stored as the binary equivalent of 60.35000000000000142108547152020037174224853515625.)
The first part of the calculation using the rounded internal binary representations (which you can see with the BINARY64. format) basically looks like this:
1.0010110110111010111000010100011110101110000101001000000*2**8 -0.0011110001011001100110011001100110011001100110011001101*2**8 =0.1111000101100001010001111010111000010100011110101110011*2**8 = 1.111000101100001010001111010111000010100011110101110011*2**7
Second, SAS subtracts 241.38 (more precisely: 241.3799999999999954525264911353588104248046875) from the above result in the same manner (the subtraction of the two zeros is redundant):
1.111000101100001010001111010111000010100011110101110011*2**7 -1.111000101100001010001111010111000010100011110101110000*2**7 =0.000000000000000000000000000000000000000000000000000011*2**7
Finally, the result is rounded to 52 mantissa bits: Note that the result has 54 binary digits after the period, so the "...011" (52nd - 54th digit) at the end are rounded up to "...100", leaving only 1 * 2**-52 * 2**7 = 2**-45.