Convert numeric into an alphanumeric value based on radix 36 - sas

It is easy to transform a number into a alphanumeric value based on radix 16 in SAS by using the $HEX format. Now i'm looking for an easy way to do this with radix 36 (10 numerals & 26 letters).
Examples:
100 -> '2s'
2000 -> '1jk'
30000 -> 'n5c'
400000 -> '8kn4'
In Java you can do this by Integer.toString(mynumber, 36). Any ideas how to do this in SAS Base?

Unfortunately there is easy way to do it usings formats, but the following data step should solve the problem. It works for positive integers only.
data _null_;
infile cards;
input innumber;
number = innumber;
format base $32.;
alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if number = 0 then base = '0';
else
do while (number ne 0);
mod = mod(number, length(alphabet));
div = floor(number / (length(alphabet)));
base = cats(substr(alphabet,mod+1,1),base);
number = div;
end;
put innumber= base=;
cards;
0
100
2000
30000
400000
;
run;

There is no built-in functionality for this. You can iteratively modulo 36 your number, then divide by 36 until what remains is zero. To convert the sequence of modulos you get you have to add 48decimal or 30hex to get the ascii-character in case of the digits 0-9 and 101decimal or 65hex to get the ascii-character in the case of the digits A-Z.

I suggest using PROC FCMP to create your own function that does the formatting. Then you can reuse the code whenever you want:
proc fcmp outlib=sasuser.funcs.Radix36;
function Radix36(innumber) $ 32; /* returns character string, length 32 */
number = innumber;
format base $32.;
alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if number = 0 then base = '0';
else do while (number ne 0);
mod = mod(number, length(alphabet));
div = floor(number / (length(alphabet)));
base = cats(substr(alphabet,mod+1,1),base);
number = div;
end;
return (base);
endsub;
run;
/*****************************************************/
options cmplib=sasuser.funcs; /* add to search path */
data _null_;
input innumber;
base = Radix36(innumber); /* call function */
put innumber= base=;
datalines;
0
100
2000
30000
400000
;
run;

Related

SAS Rounding by thousandths place

I have been asked to write some code in SAS that rounds a number up but only if the digit in the thousandth place is greater than one. For example, 78.858 would obviously round up to 78.86 but would also want to take 78.852 and round up to 78.86.
I would just do it in two operations. Use the normal ROUND() function. Then check how much it changed. And then based on that difference decide whether or not to add an extra hundredth.
Example:
data have;
input x ;
cards;
78.858
78.86
78.852
78.8515
;
data want;
set have;
round=round(x,0.01);
diff = x-round;
if diff > 0.001 then round=round+0.01 ;
run;
Results
OBS x round diff
1 78.8580 78.86 -.0020
2 78.8600 78.86 0.0000
3 78.8520 78.86 0.0020
4 78.8515 78.86 0.0015

How to convert a time (char) variable into a numeric variable?

I have a time variable that is expressed as a character in SAS. Example: 0:04 0:12 0:01 0:11 etc. I would like to convert it to a numeric variable 0.04 0.12 0.01 etc.
Using this code:
data work.set2; set work.set;
TIME2 = input(TIME, best4.);
;
run;
creates a new column with nothing but missing values. Can you advice on what to improve in my code?
SAS stores dates and times as numbers, time is the number of seconds. I think converting it to a SAS time is your best option. And there is a significant difference between 0.1 and 10 seconds because one is 6 seconds and one is 10 seconds. For example if you had 0.1 and 0.2 and took the difference that's 0.1 -> is that now a 10 or 6 second difference. You really need to think this through on how you want to interpret it and using your approach will be problematic.
The difference in times will not be reflected correctly.
Also, is 0:04 4 seconds or 4 minutes. The standard connotation would be 4 minutes, which is 240 seconds.
Here's how you can convert it:
data have;
x = '0:04';output;
x = '0:12';output;
x = '0:11'; output;
x = '1:00'; output;
x = '4:25'; output;
run;
data want;
set have;
sas_time = input(x, time.);
sas_time2 = sas_time;
format sas_time2 time4.;
/*if it's seconds*/
seconds = input(scan(x, 1, ':'), 8.)*60 + input(scan(x, 2, ':'), 8.);
run;
proc print data=want;run;
If your times are of type string:
WITHDOTS=translate(TIME2,'.',':');
Source:
https://communities.sas.com/t5/Base-SAS-Programming/Find-And-Replace-within-a-string/td-p/45104

SAS decimal precision and writing to database

Good day,
I had this issue where I was writing some numbers to database, which should have had value 0.1 in SAS, but for some bizarre reason appeared as 0.09 in SQL database. When I manually checked the dataset it showed 0.10 in format 12.2.
So what I do is check if the values are actually 0.1 or somewhat below this:
data _checking;
set publish_data;
if value < 0.1;
dummy = value*10000000;
run;
It appeared that number of observations fulfill the first condition. Ok... That explains why the values come out as 0.09. Rounding issue.
However, all dummy values come out as integers. I tried 10, 100, 1k, 10k all appear to come out as integers. (1, 10, 100 ...)
Next step I try:
data _checking2;
set _checking;
if dummy<10; /*Depending on the factorial*/
run;
This is consistent. Dummy retains the value 'a little below the value shown'.
I solved the issue by round(value,.1);
Questions:
How to observe the actual value stored in dataset? (Especially in case 'a little below')
If first condition if is true, then how can the checking with dummy still show integer values. (Because in computers epsilon has to have actual value)
2.b Or is this just a display issue? Or does SAS has flag for 'value minus epsilon'?
Answer 1:
The most precise and least human way to see the actual value is to observe the underlying IEEE bytes using HEX format.
Answer 2:
The default format for those new dummy variables is BEST12., so you won't see any small offsets if they are smaller than what best12. will show, or more precisely epsilon < 1e-(12-log10(x)). The SAS format could be considered a display issue in this case.
If your use case is that of a 'shown' value must be the actual value sent to a remote database then you will want to use ROUND prior to populating the remote tables.
data x;
x = 1/3; output;
x = 0.1 - 1e-13; output;
format x 12.2;
run;
data y;
set x;
put x= x= HEX16.;
xhex = x;
format xhex hex16.;
array dummy dummy1-dummy13;
do _n_ = 1 to 13;
dummy(_n_) = x * 10**_n_;
end;
run;
proc print data=y;
run;
data z;
do p = 0 to 10;
do q = 1 to 15;
array z z1-z15;
z(q) = 10**p + 10**-q;
end; output;
end;
drop p q;
run;
==== LOG ====
x=0.33 x=3FD5555555555555
x=0.10 x=3FB9999999997D74
==== PRINT ====
Obs x xhex dummy1 dummy2 dummy3 dummy4 dummy5 dummy6 dummy7
1 0.33 3FD5555555555555 3.33333 33.3333 333.333 3333.33 33333.33 333333.33 3333333.33
2 0.10 3FB9999999997D74 1.00000 10.0000 100.000 1000.00 10000.00 100000.00 1000000.00
Obs dummy8 dummy9 dummy10 dummy11 dummy12 dummy13
1 33333333.33 333333333.33 3333333333.3 33333333333 333333333333 3.3333333E12
2 10000000.00 100000000.00 1000000000.0 10000000000 100000000000 999999999999
You can try a different format. try 32.31 or best32.
Subtract 0.1-value and look at the result. Again, use a format with a lot of decimal places.
You are probably not seeing the value in the dummy variables because the epsilon is very small and the dummy is still getting rounded for display.
Try dummy=value*1e16 or higher.
Numbers in SAS are C doubles, fwiw.

SAS - Why it is displaying the values of len1 to len3 as 12 in output

Why it is displaying the values of len1 to len3 as 12 in output?
data champ;
array len[4] len1-len4;
do i = 1 to 4;
len[i] = lengthn(len[i]);
end;
run;
Because you used a character function on a numeric variable. So SAS converted the number to a character string and reported the length of the generated string. You should have seen this note in the SAS log.
NOTE: Numeric values have been converted to character values at the
places given by:
By default SAS will use BEST12. format to convert the numbers to a string. Hence the returned value was always 12 characters long.

Creating a '000s (thousands) format in SAS

I'm trying to create a format that will convey a sense of scale for the data I'm working with. If the number is >= 1000 I want to add K as a suffix, and represent it in '000s. Eg. 123456 would be 123K.
This is what I have so far but clearly I'm misunderstanding something fundamental becuase the numbers going in don't look anything like the numbers coming out:
proc format;
picture abbrev
0 = '0'
0 -< 1000 = '000'
1000 - high = '0,000,000k' (mult=.0001)
;
run;
%put %sysfunc(sum(123456.0),abbrev.) /*SHOULD BE SOMETHING LIKE 123K */
%sysfunc(sum(12345.60),abbrev.) /*SHOULD BE SOMETHING LIKE 12.3K */
%sysfunc(sum(1234.560),abbrev.) /*SHOULD BE SOMETHING LIKE 1.3K */
%sysfunc(sum(123.4560),abbrev.) /*SHOULD BE SOMETHING LIKE 123 */
%sysfunc(sum(12.34560),abbrev.) /*SHOULD BE SOMETHING LIKE 12 */
%sysfunc(sum(1.234560),abbrev.) /*SHOULD BE SOMETHING LIKE 1 */
;
First, you made a math error; mult is .001 for 1000, not .0001.
Second, if you want the decimal, you have to understand picture a bit better. Picture is what it says: a picture. It does not have the 'add decimals as necessary' of something like BEST., or even COMMA. It only provides the decimal you ask for in the picture. But, you also have to adjust your mult for it - the picture takes the digits left of the decimal only, and places a new decimal on top of it.
With no decimal (see the .001):
proc format;
picture abbrev
0 = '0'
0 -< 1000 = '000'
1000 - high = '0,000,000k' (mult=.001)
;
run;
%put %sysfunc(sum(123456.0),abbrev.) /*SHOULD BE SOMETHING LIKE 123K */
%sysfunc(sum(12345.60),abbrev.) /*SHOULD BE SOMETHING LIKE 12.3K */
%sysfunc(sum(1234.560),abbrev.) /*SHOULD BE SOMETHING LIKE 1.3K */
%sysfunc(sum(123.4560),abbrev.) /*SHOULD BE SOMETHING LIKE 123 */
%sysfunc(sum(12.34560),abbrev.) /*SHOULD BE SOMETHING LIKE 12 */
%sysfunc(sum(1.234560),abbrev.) /*SHOULD BE SOMETHING LIKE 1 */
;
With a decimal (see it's now .01, since the decimal comes from the ones digit after multiplying):
proc format;
picture abbrev
0 = '0'
0 -< 1000 = '000'
1000 - high = '0,000,000.0k' (mult=.01)
;
run;
%put %sysfunc(sum(123456.0),abbrev.) /*SHOULD BE SOMETHING LIKE 123K */
%sysfunc(sum(12345.60),abbrev.) /*SHOULD BE SOMETHING LIKE 12.3K */
%sysfunc(sum(1234.560),abbrev.) /*SHOULD BE SOMETHING LIKE 1.3K */
%sysfunc(sum(123.4560),abbrev.) /*SHOULD BE SOMETHING LIKE 123 */
%sysfunc(sum(12.34560),abbrev.) /*SHOULD BE SOMETHING LIKE 12 */
%sysfunc(sum(1.234560),abbrev.) /*SHOULD BE SOMETHING LIKE 1 */
;
See SAS Documentation for more details and a good example.