@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...
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!
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.