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 2025: Save the Date

 SAS Innovate 2025 is scheduled for May 6-9 in Orlando, FL. Sign up to be first to learn about the agenda and registration!

Save the date!

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
  • 5029 views
  • 20 likes
  • 6 in conversation