BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
ChrisNZ
Tourmaline | Level 20
Ha. Thank you for the compliment. Glad you like it. Not my algorithm though! But pretty nifty: There is no theoretical length limit except 32k. You can still work on a solution for non integers if you want, though I have trouble imagining what A.A hex is in base 10. 10+10/11?
Do share if you write it! 🙂
FreelanceReinh
Jade | Level 19

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

ChrisNZ
Tourmaline | Level 20

@FreelanceReinh  Here is a slightly better version, while awaiting yours. 🙂

 

FreelanceReinh
Jade | Level 19

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:

Spoiler
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.

ChrisNZ
Tourmaline | Level 20

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! 

 

FreelanceReinh
Jade | Level 19

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.

ChrisNZ
Tourmaline | Level 20

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!

 

Spoiler

 

  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! */

 

FreelanceReinh
Jade | Level 19

Thanks, @ChrisNZ! Nice addition to my function. A specialist tool's manual for the occasional ordinary user. :-)

ChrisNZ
Tourmaline | Level 20

@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-wordmark-2025-midnight.png

Register Today!

Join us for SAS Innovate 2025, our biggest and most exciting global event of the year, in Orlando, FL, from May 6-9. Sign up by March 14 for just $795.


Register now!

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.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 24 replies
  • 5594 views
  • 20 likes
  • 6 in conversation