SAS lopping off a digit at the end of a decimal point

Viewed 211

I am trying to add a variable that has the same value for every record. I created about 10 of these variables (each with a different value). For one of them, SAS is not keeping the last digit (1). I have tried adjusting the format to make it longer, but instead of adding the 1, it just adds additional zeros to the right.

here is example of code:

data createnewvars;
  set original;
  
variable1=39.5652749413775;
variable2=0.359365098601411;

format variable1 17.13 variable2 18.15;
run;

When I look at the data, the value for variable2 = 0.359365098601410. FYI, when I shorten the format (ex. 17.13) value for variable2=0.3593650986014 which is to be expected.

Any ideas how to fix this?

1 Answers

First: the actual answer to your problem; then a digression about how numbers are stored, in case you're interested.

SAS is able to store the number you're showing above fine (or, well enough, anyway), but it's hiding a bit of precision from you, basically trying to be nice - doing math in the 15th decimal digit is tricky, given you have little room before you lose actual precision.

However, there's an option that tells SAS to behave itself and show its full capabilities. The DECIMALCONV system option. By default it's set to COMPAT, which is in keeping with older versions of SAS; however, if you set it to STDIEEE, it will show you all of the digits of your above number. It won't show you much more, note; you really are at the limits of precision. This was added in SAS 9.4, so don't use this if you have 9.3 or earlier still hanging around at your place of business. The option may also be locked down by your Sysadmin, so check with them if you're in a server environment and you find the option not allowed or not working.

See these two data steps in action:

options decimalconv=compat;
data _null_;
  variable2=0.35936509860141;
  variable3=0.359365098601411;
  put variable2= best18. / variable3= best18.;
run;

 variable2=0.35936509860141
 variable3=0.35936509860141

options decimalconv=stdieee;
data _null_;
  variable2=0.35936509860141;
  variable3=0.359365098601411;
  put variable2= best18. / variable3= best18.;
run;


 variable2=0.35936509860141
 variable3=0.359365098601411

Like magic, variable3 is now displayed with that extra 1 you asked for. I can't emphasize enough, though, that this does not affect what is stored in the variable; it only affects how it's shown on the screen (and wherever else you use it) - it's always the same number under the covers, and it's almost-but-not-exactly what you wan, as with most floating point numbers. It's also right at the edge of precision, so that 'almost-but-not-exactly' may matter if you're doing further math with it. If you need this great of precision and further, SAS may not be the ideal language for your work.


Now, to explain what's going on here. SAS is capable of storing numbers with precision up to 2^53, which is almost 16 decimal digits of precision. However, SAS tends not to display quite so neatly when you're hovering over the limits of precision, because of rounding issues related to powers of two - since the numbers are stored in binary, it's not quite so neat to store an exact decimal number, just like storing "1/3" in decimal is not feasible. But the number is still there, all the same.

In your example, SAS does store those two numbers differently, despite the fact that it does not display them differently. See the following, extending from your example:

data createnewvars;

  
variable1=39.5652749413775;
variable2=0.35936509860141;
variable3=0.359365098601411;

put variable2= best18. / variable3= best18.;
put variable2= hex16. / variable3= hex16.;
put variable2= binary64. / variable3= binary64.;

equal = variable2 - variable3;
run;

This results in:

 variable2=0.35936509860141
 variable3=0.35936509860141
 variable2=3FD6FFD67874479F
 variable3=3FD6FFD6787447B1
 variable2=0011111111010110111111111101011001111000011101000100011110011111
 variable3=0011111111010110111111111101011001111000011101000100011110110001

Note that the hexidecimal representation varies slightly at the end. That's the end of the mantissa, or the "precision" part of SAS's storage. (This is assuming you're running on Linux/Unix/Windows, by the way; on an IBM Mainframe, this will be somewhat different, but analogous in high level concept.) And in fact, it differs by about 1x10-15, when multiplied by the various powers of two appropriate - take a look at equal in the example, that's awfully close to that number - but we'll get to that later.

The part that differs are the last 6 digits: 011111 for the shorter number, and 110001 for the longer number, specifically. These digits are each multiplied by a power of two defined by the Sign portion of the number, which in both cases is 01111111101 - the decimal number 1021, which is then offset by -1023, for a final result of -2. That means the first digit (the 13th digit in the binary representation) represents 2-3, the next digit 2-4, and so forth.

The last 6 digits, then, represent 2-49 to 2-54. 110001 - 011111 in binary is 010010, which is 2-50 plus 2-53. Those two numbers added together and converted to decimal are 9.99x10-16, or right about your 1x10-15 that you lost track of. Also, remember the equal variable from the SAS code above? Take a look at the log - it in fact is equal to that.

So - why can't SAS show you that last digit? It's because it's not exactly equal to that last 1 - and it's off by less than the amount SAS can cleanly store (in your example, by less than 2-54). As such, SAS doesn't want to lie to you that it is able to store that safely and precisely, so it hides the truth from you.

Related