BookmarkSubscribeRSS Feed
Calcite | Level 5



data aa;

  input a b;
  c = b - a;
  if c = 6.3;
  2.9 9.2




Mathematically c should have been 6.3 (9.2 - 2.9), however, the above code is giving zero observation as the results is not equal to 6.3 at some decimal level.


I have tried the same code for the following differences and similar issues were observed.

2.01 - 1.02

5.4 - 4.5 ...


Have anyone faced similar issues?


SAS Employee

Absolutely! This has been discussed in several posts and here's a great solution to read through. 


If you want to see what's going on behind the scenes, display your calculated value (b-a) and the value you wish to compare against (let's call this d) in their hexidecimal representation using the HEX format, like so:


data aa;
  input a b;
  c = b - a;
  d = 6.3;

  put c hex16.;
  put d hex16.;

  2.9 9.2

You can see that the two values are very similar, except for the end due to rounding errors. SAS Documentation covers this topic here: 


For your example, the ROUND function will do the trick like so:  

data aa;
  input a b;
  c = b - a;
  d = 6.3;

  if round(c,.1) = d;

  2.9 9.2


Jade | Level 19

Hi @amalap and welcome to the SAS Support Communities (belatedly)!


Sorry to see that your question had not been answered for a couple of days. This is very unusual for this rather elementary type of question, so I suspect that some technical delay must have prevented the forum experts and myself from seeing your post early.


Now you have already received a link to a relevant page of the documentation and the practical solution of using the ROUND function.


Let me just add a few bits (pun intended) to explain the "wrong results" which you observed in more detail.


The numbers in your first example, 9.2 and 2.9, have one decimal place each. Any such integer multiple of one tenth with a decimal place other than 0 or 5 is affected by numeric representation error, which means that the number's internal binary floating-point representation used by SAS (and not only SAS) to store numeric values is not 100% exact due to rounding. Indeed, the decimal equivalents of the internal 64-bit representations of 9.2 and 2.9 under Windows or Unix are




respectively. Well, you need to be lucky to get perfect results from a calculation starting with inexact values like these. As we will see below, this example is actually close to such a "lucky" case.


Written in the binary system, both 9.2 and 2.9 are periodic fractions:

1001.0011001100110... (mathematical binary representation of 9.2)
  10.1110011001100... (mathematical binary representation of 2.9)

where the 4-digit patterns 0011 and 1100, respectively, are repeating infinitely often. Given the limited storage space for a numeric variable (of length 8 bytes), these numbers are rounded in order to obtain the best approximation that fits into 53 bits: the so called "implied bit" for the trivial leading 1 and the 52 mantissa bits (see documentation) for the remaining binary digits (of integer and fractional part). The remaining 64−52=12 bits of storage space accommodate the sign and the exponent (i.e. the order of magnitude).


put(9.2, binary64.)='0100000000100010011001100110011001100110011001100110011001100110'
put(2.9, binary64.)='0100000000000111001100110011001100110011001100110011001100110011'

Note that both numbers were rounded off because the next digit was zero, hence the slightly "too small" decimal equivalents.


For the subtraction (which can be done manually like in elementary school, the repeating patterns reducing the effort considerably), the representation of 2.9 is adapted to the exponent of the larger number: basically, both numbers are now written as multiples of 2**3=8.


Thanks to the 80 bits available to the processor, the right shift of 2.9 by two bits does not lead to additional truncation or rounding. As a consequence, however, the rightmost bit 1 hits a zero bit from 9.2 which was due to rounding for the binary64. representation in the previous step, and this causes the damage. (There is still a bit of hope at this point, though, because of the rounding in the final step, described below.)


Finally, the result is "normalized," i.e., written as 1.10010011001... * 2**2 (again, a periodic fraction, the repeating pattern being 1001) and rounded to 52 mantissa bits. Only the very last "1" does not fit into the mantissa, so this is a borderline case like ".5" in the decimal system! Rounding up would lead to the correct result (the internal representation of 6.3), but -- unfortunately in this case -- the rule in these borderline situations is "round to the even number," i.e., ending in 0. So the result is the internal representation


with that rounding error in the last bit making it slightly less than the internal representation of 6.3. (Which, of course, ends in ...10011, correctly rounding the infinite sequence of 1001's.)


Your other two examples can be explained in exactly the same way. The case of 5.4 − 4.5 is particularly easy because, unlike all the other numbers, 4.5 (ending in .5) has an exact, finite binary representation -- 100.1 --, so you'll subtract many trailing zeros in the manual calculation.



Registration is open! SAS is returning to Vegas for an AI and analytics experience like no other! Whether you're an executive, manager, end user or SAS partner, SAS Innovate is designed for everyone on your team. Register for just $495 by 12/31/2023.

If you are interested in speaking, there is still time to submit a session idea. More details are posted on the website. 

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.

Get the $99 certification deal.jpg



Back in the Classroom!

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

View all other training opportunities.

Discussion stats
  • 2 replies
  • 3 in conversation