BookmarkSubscribeRSS Feed
raja777pharma
Fluorite | Level 6

Hi Team,

 

Please help me to display the all decimals exist in numeric variables.

 

I am using below code to convert the numeric values to character to see how many decimals, but how the same values not populated in numeric variables AVAL and CHG.

 

How to display the all decimals for numeric variables. 

 

data out;
  set in;

  aval_c=put(aval,best32.);
  chg_c=put(chg,best32.);

run;

 

raja777pharma_0-1665467098803.png

 

 

Thank you,

Rajasekhar.

 

 

9 REPLIES 9
Kurt_Bremser
Super User

What do you mean by "all decimals"?

Mathematically, this is impossible, because most numbers have an infinite number of decimals (e.g. 1/3).

Supply examples, and the intended (final) results.

FreelanceReinh
Jade | Level 19

Hi @raja777pharma,

 


@raja777pharma wrote:

How to display the all decimals for numeric variables. 

 

data out;
  set in;

  aval_c=put(aval,best32.);
  chg_c=put(chg,best32.);

run;

 

raja777pharma_0-1665467098803.png


As a rule, none of the common numeric formats (BESTw., w.d, Ew., etc.) will show you "all decimals for numeric variables" in many cases. They all do some sort of rounding or truncation and thus conceal parts of the content of the variable. The easiest way to see what exactly is contained in a numeric variable is to display it in a format such as HEX16. or BINARY64. showing the internal (binary floating-point) representation. This is sufficient for exact comparisons of two values, but you see only hexadecimal or binary digits.

 

However, if you want to see all decimals, you need to

  1. translate the digit string which constitutes the internal representation into a number written in the binary (or hexadecimal) system
  2. write this number in the decimal system.

The good news is that all (non-missing) values of numeric variables have a finite decimal representation. So, cases like 1/3 with an infinite number of decimals do not occur. (As in the decimal system, 1/3 does not have a finite binary representation, whereas the internal representations are limited by the available 8 bytes for a numeric variable and hence are finite.)

 

Unfortunately, I have not yet coded a function that displays the exact decimal value contained in a numeric variable (but this has been on my to-do list for quite a while), so for the time being this is a partly manual process using the BINARY64. format, ChrisNZ's macro CONVERTBASE attached to his 2020 post Re: Converting a 19 length number to hex and then to a string (only needed if there's a substantial integer part, not for your small example numbers) and mainly the function BASECONVFP that I posted in the same thread: ...Converting-a-19-length-number-to-hex-and-then-to-a-string/m-p/654626/highlight/true#M196566.

 

Typically, several different values of a numeric variable share the same BEST32. representation -- where "several" can be a small number like 5, but also a number in the millions (!). Contrary to naïve expectations, BEST32. is not always the best choice if you want to see the most precise value possible with a common, built-in format. I've seen many cases where BEST16. (!) or a suitable w.d format was superior to BEST32. Also, the rarely used DECIMALCONV= system option has a substantial impact. With its default setting COMPATIBLE my Windows SAS 9.4M5 displays

    23 different values as  3.76082040434571
    11 different values as  4.04399223570895
184467 different values as -0.00029820160976
184467 different values as  0.00030661712631

in the BEST32. format. The DATA step below writes the first and last number of each of the four ranges and the neighboring internal representations with a different BEST32. display to the log:

1158  data _null_;
1159  input x hex16.;
1160  put x hex16. x best32.;
1161  cards;

400E162902165AC2                 3.7608204043457
400E162902165AC3                3.76082040434571
400E162902165AD9                3.76082040434571
400E162902165ADA                3.76082040434572
40102D0C4CF69768                4.04399223570894
40102D0C4CF69769                4.04399223570895
40102D0C4CF69773                4.04399223570895
40102D0C4CF69774                4.04399223570896
BF338AFE295A46AF               -0.00029820160975
BF338AFE295A46B0               -0.00029820160976
BF338AFE295D1742               -0.00029820160976
BF338AFE295D1743               -0.00029820160977
3F34182E87A7320C                 0.0003066171263
3F34182E87A7320D                0.00030661712631
3F34182E87AA029F                0.00030661712631
3F34182E87AA02A0                0.00030661712632

(The left column is identical to the data lines after the CARDS statement.)

 

The smallest and largest internal representation displayed as 3.76082040434571 in BEST32. (400E162902165AC3 and 400E162902165AD9, resp.), i.e., like your first AVAL value, written in the decimal system, are:

3.760820404345709189186663934378884732723236083984375
3.760820404345718959149280635756440460681915283203125

The difference between these two numbers equals 11*2**-50=9.76996...E-15. As you can see, the BEST32. representation (ending in ...571) of the larger value is not mathematically rounded to ...572.

 

While those exact decimal representations are of interest to fans of long numbers (like me), the internal binary or hexadecimal representations are sufficient for most practical purposes, as mentioned earlier.

PaigeMiller
Diamond | Level 26

What possible reason would you have to convert "all decimal places" of a number to character?

--
Paige Miller
Tom
Super User Tom
Super User

The main point is they ARE the same values.

The issue is that you printed the original numbers using the BEST12. format and generated the string using the BEST32. format.  So of course they LOOK different.

 

SAS can only store 15 decimal digits exactly.  If you want a method to generate a character variable that will result in the same value when converted back into a number then perhaps you want something like this?

The basic idea is to figure out the magnitude of the number using the LOG base 10.  Then use that information decide how many digits you need to generate using the normal numeric format rather than the BEST format.

 

data have;
  input @1 B @1 raw $32.;
cards;
  3.76082040434571
  4.04399223570895
 -0.000298201609761234
  0.000306617126311234
  3.76082040434571
  37.6082040434571
  376.082040434571
;

data want;
  set have ;
  exp = floor(log10(abs(b)));
  bformat = cats('F',min(32,max(17,17-exp)),'.',min(31,max(0,14-exp)));
  bstring = putn(b,bformat);
  put  raw exp= b= :best32. / bstring /;
run;

Results:

475  data want;
476    set have ;
477    exp = floor(log10(abs(b)));
478    bformat = cats('F',min(32,max(17,17-exp)),'.',min(32,max(0,14-exp)));
479    bstring = putn(b,bformat);
480    put  raw exp= b= :best32. / bstring /;
481  run;

3.76082040434571 exp=0 B=3.76082040434571
3.76082040434571

4.04399223570895 exp=0 B=4.04399223570895
4.04399223570895

-0.000298201609761234 exp=-4 B=-0.00029820160976
-0.000298201609761234

0.000306617126311234 exp=-4 B=0.00030661712631
0.000306617126311234

3.76082040434571 exp=0 B=3.76082040434571
3.76082040434571

37.6082040434571 exp=1 B=37.6082040434571
37.6082040434571

376.082040434571 exp=2 B=376.082040434571
376.082040434571

So you can see the generated BSTRING values match exactly the original RAW strings read originally.

 

FreelanceReinh
Jade | Level 19

Thanks, @Tom, for pointing out that @raja777pharma's main issue was maybe just the difference between BEST12.- and BEST32.-formatted values. I hadn't thought it could be that simple.

 


@Tom wrote:

If you want a method to generate a character variable that will result in the same value when converted back into a number then perhaps you want something like this?

The basic idea is to figure out the magnitude of the number using the LOG base 10.  Then use that information decide how many digits you need to generate using the normal numeric format rather than the BEST format.


I think even your fine approach cannot overcome the limitations of the common numeric formats (including the "normal" Fw.d format) that I mentioned in my previous post. In your examples the equality

input(bstring, 32.)=b

holds indeed. But often, as we all know, numeric values with that many decimals rather arise from calculations than from reading numeric literals. It appears that @raja777pharma's AVAL and CHG values are a case in point. They appear to be base-10 logarithms of rational numbers:

data have(keep=b);
input x y;
b=log10(y); output;
b=b-log10(x); output;
cards;
5769.24 5765.28
11058.23 11066.04
;

Using the above HAVE input dataset (whose values of b, viewed in BEST32. format, look exactly like the AVAL_C and CHG_C values from the initial post) the equality input(bstring, 32.)=b breaks down in all four cases (at least on my Windows SAS 9.4M5).

 

For "a method to generate a character variable that will result in the same value when converted back into a number" I would suggest using the RB8. format:

bstring=put(b, rb8.);

Then the equality

input(bstring, rb8.)=b

should hold for all possible numeric values of b (including ordinary and special missing values). The HEX16. format/informat (which, unlike the RB8. format, are available in CAS) should work as well, except for special missing values.

Tom
Super User Tom
Super User

I suspect the intent was to have a human readable character string.

 

To account for differences between numbers generated by calculations and reading text strings then perhaps you could replace the original numeric value with the value generated by reading back in the generated string?

FreelanceReinh
Jade | Level 19

@Tom wrote:

... perhaps you could replace the original numeric value with the value generated by reading back in the generated string?


To avoid a loss of accuracy I would rather try to find a numeric literal that, read by SAS, exactly matches the original value. On my computer the literals below (with 16-17 significant digits) satisfy this condition for the example numbers (logarithms).

 3.760820404345718
-0.00029820160976035126
 4.043992235708956
 0.00030661712631019354

For the two numbers close to zero the last decimal is not uniquely determined.

 

It might be possible to create a user-defined format (based on a user-defined function) that yields such literals. Maybe that would be what @raja777pharma needs.

 

Neither the decimal equivalents of the internal representations

 3.760820404345718070970860935631208121776580810546875
-0.000298201609760351260547395213507115840911865234375
 4.0439922357089557181097916327416896820068359375
 0.00030661712631019355512762558646500110626220703125

nor the 32-digit results (logarithms) of the Windows calculator

 3.7608204043457181236947220335419
-2.9820160976030282536702286682087e-4
 4.0439922357089559295145311670842
 3.0661712631004661141724628180561e-4

can substitute those literals.

Tom
Super User Tom
Super User

You seem to be trying to handle numbers that SAS cannot exactly represent because it uses 64 bit binary numbers.

For example, that small negative number (-0.00029820160976035126) has more digits than SAS can exactly represent.

633  data test;
634    integer=29820160976035126 ;
635    max_int=constant('exactint');
636    put (_all_) (=comma30./);
637  run;

integer=29,820,160,976,035,128
max_int=9,007,199,254,740,992
FreelanceReinh
Jade | Level 19

Sure. Yet, reading this long literal -- and no shorter literal -- into a numeric variable by an assignment or INPUT statement (or by the INPUT function with informat 32.) yields the exact same internal representation as the expression log10(5765.28)-log10(5769.24). I know that too long numeric literals can lead to unexpected results.

SAS Innovate 2025: Call for Content

Are you ready for the spotlight? We're accepting content ideas for SAS Innovate 2025 to be held May 6-9 in Orlando, FL. The call is open until September 25. Read more here about why you should contribute and what is in it for you!

Submit your idea!

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
  • 9 replies
  • 5291 views
  • 1 like
  • 5 in conversation