@ChrisNZ wrote:
There is no theoretical length limit except 32k.
Yes, this is particularly impressive about it.
You can still work on a solution for non integers if you want, ...
Maybe I can focus on numbers between 0 and 1. Including the integer part for arbitrary positive numbers would just mean to prepend the result from your macro.
@ChrisNZ wrote:
... I have trouble imagining what A.A hex is in base 10. 10+10/11?
10+10/16=10.625.
@FreelanceReinh Here is a slightly better version, while awaiting yours. 🙂
Great, @ChrisNZ, thanks again, also for motivating me to write code for the fractional part (attached). I went for a PROC FCMP approach, just to try a different route.
A few test cases:
data test;
input inbase outbase outlen fp :$80.;
length cfp $100;
cfp=baseconvfp(fp,inbase,outbase,outlen);
cards;
10 2 24 2
10 8 8 2
10 16 6 2
32 16 5 TEST
10 7 55 142857142857142857142857142857142857142857143
8 10 22 4743357156277512370127634
16 10 25 0123456789ABCDEFGHIJKL
111 10 30 1
111 10 30 11111111111111
28 36 3 M08A
;
Result:Obs inbase outbase outlen fp cfp
1 10 2 24 2 001100110011001100110011
2 10 8 8 2 14631463
3 10 16 6 2 333333
4 32 16 5 TEST EBB9D
5 10 7 55 142857142857142857142857142857142857142857143 1000000000000000000000000000000000000000000000000000004
6 8 10 22 4743357156277512370127634 6180339887498948482045
7 16 10 25 0123456789ABCDEFGHIJKL 0044444444444444444444444
8 111 10 30 1 009009009009009009009009009009
9 111 10 30 11111111111111 009090909090909090909090909090
10 28 36 3 M08A SAS
This version doesn't contain parameter checks. For people who know what they're doing it can even be useful to go beyond the "limits": For example, "invalid" digits such as G, H, etc. in a hexadecimal input are reasonably interpreted as 16, 17, etc. (see 7th test case: computes 1/16² + 2/16³ + ... + 21/16²² ≈ 1/15² = 0.0044444...). Similarly, bases >36 are no problem (especially on the input side) as long as the digits are not too large. (Application: Compute 1/n to many decimals by converting "1", i.e. 0.1, from base n to base 10. See 8th test case for n=111.)
As mentioned earlier, converting an arbitrary number >=0 is now as simple as concatenating two strings. Example:
%let hex=7E4.0F91;
%let dec=%ConvertBase(16,10,%scan(&hex,1)).%sysfunc(baseconvfp(%scan(&hex,2),16,10,4));
%put &=dec; /* DEC=2020.0608 */
I'm really glad to have these new tools available. They will be useful for investigating numeric representation issues among other things. Thanks @sascam for sparking this discussion.
Very very nice!!! I am so impressed once again!
I tried to process a 32k string and it's about twice as fast as the integer calculator.
Kudos!
Thanks! Luckily, the algorithm was pretty simple. I also learned new things about PROC FCMP while developing this code, e.g., the dynamic array feature. A potential combined conversion of integer and fractional parts could likely take advantage of the similarities between the algorithms.
I can see the attractivity of creating a FCMP function rather than a macro function. It's probably more versatile.
Creating a help screen is more difficult though, and I uncovered 2 bugs.
No optional parameters also makes it harder for users to call the help screen.
Anyway here is the first draft. It's not what I wanted but that's what we have for now. Feel free not to use!
function BASECONVFP(F $, B, C, N) $32767;
DIGITS='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
if upcase(F) in(' ','HELP') then
HELP='HELP';
else if ^(2 <= B <= 36) then do;
put 'ERROR: FROM base must be between 2 and 36.';
HELP='HELP';
end;
else if ^(2 <= C <= 36) then do;
put 'ERROR: TO base must be between 2 and 36.';
HELP='HELP';
end;
else if ^prxmatch('/\A['||substr(DIGITS,1,B)||']+\Z/',F) then do;
put 'ERROR: Please provide a positive integer to convert as a fractional value.';
put 'ERROR- In base' B 'only digits' (substr(DIGITS,1,B)) 'are meaningful.';
HELP='HELP';
end;
else if ^(1 <= N <= 32767) then do;
put 'ERROR: The number of fractional places must be between 2 and 32767.';
HELP='HELP';
end;
if HELP='HELP' then do;
LINE=repeat('#',99);
put '00'x;
put LINE;
put '# ___________________________________ #';
put '# Help screen for function BaseConvFP #';
put '# ----------------------------------- /\"_"/\ #';
put "# ( ='.'= ) #";
put '# This function converts the fractional part of a number from a base to another base. (") "" (") #';
put '# The allowable bases are 2 to 36. |" "| #';
put '# Very long (up to 32k in length) fractional numbers can be converted. /"/ \"\ #';
put '# Note: The returned string is always 32k in length, (")"||"(") #';
put '# so set the variable length beforehand. (( #';
put '# )) #';
put '# Parameters (( #';
put '# ========== #';
put '# str REQD Fractional number to convert. #';
put '# A string of base-TO digits that is 32k maximum in length. #';
put '# #';
put '# from REQD Base from which the number is converted. #';
put '# Integer between 2 and 36. #';
put '# #';
put '# to REQD Base to which the number is converted. #';
put '# Integer between 2 and 36. #';
put '# #';
put '# n REQD Number of output digits in the TO base. #';
put '# #';
put '# Examples #';
put '# ======== #';
put '# #';
put '# 1. Display this help screen #';
put '# --------------------------- #';
put '# data T; A=BaseConvFP("help",0,0,0) ;run; #';
put '# data T; A=BaseConvFP("0",0,0,0) ;run; #';
put '# #';
put '# 2. Convert number 0.008 from base 10 to base 16 with a precision of 25 digits #';
put '# ----------------------------------------------------------------------------- #';
put '# data T; length A $25; A=BaseConvFP("008",10,16,25); put A=; run; #';
put '# writes: #';
put '# A=020C49BA5E353F7CED916872B #';
put '# #';
put LINE;
put '00'x;
return(' ');
end;
/* Untouched logic from FreelanceReinhard's program! */
Thanks, @ChrisNZ! Nice addition to my function. A specialist tool's manual for the occasional ordinary user. :-)
@FreelanceReinh Fyi the tech support work about the uncovered bugs is still ongoing.
One interesting fact that came to the fore is that FCMP's put statement's argument is not a plain string but a format string that gets parsed.
So I could not write
put '%put %sysfunc(BaseConvFP("0",0,0,0));' ;
in the help section as %p and %s are parsed as pointers.
I need to write
put '%%put %%sysfunc(BaseConvFP("0",0,0,0));' ;
Fascinating...
SAS Innovate 2025 is scheduled for May 6-9 in Orlando, FL. Sign up to be first to learn about the agenda and registration!
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.
Ready to level-up your skills? Choose your own adventure.