BookmarkSubscribeRSS Feed
shashank12
Fluorite | Level 6

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.

7 REPLIES 7
FreelanceReinh
Jade | Level 19

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);
shashank12
Fluorite | Level 6

@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.

FreelanceReinh
Jade | Level 19

@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.

Kurt_Bremser
Super User
This is caused by the limits of binary mathematics. Decimal fractions cannot be accurately stored with binary digits. Google „SAS numeric precision“ for the technical details.

As a remedy, use the ROUND function to get rid of the artifacts.
FreelanceReinh
Jade | Level 19

@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.

sas-innovate-2024.png

Available on demand!

Missed SAS Innovate Las Vegas? Watch all the action for free! View the keynotes, general sessions and 22 breakouts on demand.

 

Register now!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 7 replies
  • 1085 views
  • 5 likes
  • 3 in conversation