*/
Stuck? Need help? Ask questions on our forums.
*/

View \BASE.C

Base Conversion of Numbers

Submitted By: WEBMASTER
Rating: starstar (Rate It)


#if 0
Base Conversion of Numbers
Jon R. Herbold

[C programmers must be able to work with binary, octal, decimal, and
hexadecimal numbers.  This article describes a general purpose conversion
and formatting routine for numbers in these (and other) bases.]

     A general purpose base conversion and formatting routine would find a use
in such places as programmer's calculators and memory dump utilities. To
meet these needs, I have implemented, nBaseConvert(), a flexible conversion
routine that can convert numbers to and from any base in the range 2-36. In
addition, this routine can (optionally) maintain about 50 bits of internal
precision, thus allowing conversion of very large numbers.

     The usage and calling sequence for nBaseConvert() are documented and
demonstrated in the code listing.  In brief, it is passed a string to
convert, a place to put the result, the "to" and "from" bases, and some
formatting information.  The characters 0-9 followed by A-Z are used to
represent the result--this is a natural extension of the system used to
represent hexadecimal values.  The conversion proceeds in a straightforward
manner.  First, the input string is scanned character by character and its
value is converted to an internal numeric form.  Then, the process is
reversed to generate a number in the target base.

     The selection of an internal numeric representation for the value being
converted presented an interesting dilemma.  There were two natural choices,
each with its own strong points.  The first was to use "long double" and take
advantage of the 52 bits of precision in the mantissa.  This would give the
routine the ability to convert very large numbers.  The other possibility
would be to use "unsigned long".  This would allow conversion of numbers up
to 32 bits long while not requiring that the bulky floating point routines
be linked into the executable code.  As a compromise, both forms are available--
simple select the needed version with the compile time switch USE_FLOAT.

     Once the number has been converted to the target base, nBaseConvert() has the
ability to format it.  First, the output string can be broken up into
segments of n digits, where n is different for each base.  For example, n for
base 10 should be 3, while n for base 2 is usually 8.  The character that is
used to separate the segments is passed as an argument to nBaseConvert(). 
The array bBatchArray defines the values of n for each base.

     nBaseConvert() can also pad the output string with leading zeros.  Here
again it refers to bBatchArray to determine how many digits are grouped in
the target base and thus how many leading zeros need to be supplied.


ADDENDUM

     I use a systematic naming scheme for my variables and functions that
requires some explanation. I have found it to be most useful in reading and
debugging code--there is nothing more frustrating that trying to figure out
the name or precise return value of a function that was written previously.
This can be avoided by assigning function name using the following rules:

o  The ANSI standard now provides that function names can be as long as 31
   characters.  Take advantage of this by assigning meaningful function names.
o  Function names should be assigned from most general to most specific. The
   following illustrates this principle:

        nPrinterInitialize()
        nPrinterStatusGet()
        nPrinterCharacterRead()
        nPrinterCharacterWrite()

    In this way, when the functions are listed in alphabetical order, all
    related function names appear together. Sorting should begin with the
    first upper-case character.

     In addition, it very important to know the data type of a variable or
returned by a function.  Thus, I preceed each variable and function name with
a prefix that provides this information.  The prefixes that I use are:

     PREFIX     DESCRIPTION
      fn        function
       v        void
       a        array
       s        structure
       u        union
       p        pointer
     
       c        char
       n        int
       s        short
       l        long
       f        float
       d        double
      ld        long double

     These prefixes are assigned using the standard right-left rule.
The following are examples of how these prefixes are used with variables.
The same principle applies to functions.

    char cChar;
    char acString[50];
    char *pcPointer;
#endif



/* vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv */
/*  Copyright (c) 1988; Jon R. Herbold                                      */
/*  Indianapolis, IN 46240 ALL RIGHTS RESERVED                              */
/*                                                                          */
/*  FILE NAME:                  FUNCTION NAME:                              */
/*     basecvt.c                   int = nBaseConvert( )                    */
/*                                                                          */
/*  DESCRIPTION:                                                            */
/*     This function converts numbers from one base to another base.  Both  */
/*     bases must be in the range of 2 - 36.  Additional parameters allow   */
/*     for the formating of the output.                                     */
/*                                                                          */
/*  PARAMETERS:                                                             */
/*    char    *pcNumberToConvert;      ASCIIZ number to be converted        */
/*    int     nSourceBase;             base of pcNumberToConvert            */
/*    int     nDestinationBase;        result base                          */
/*    char    *pcResult;               ASCIIZ result of conversion.  Must   */
/*                                     be MAX_LENGTH_OUTPUT+1 chars long.   */
/*    int     nSeparateOutput;         This parameters specifies whether or */
/*                                     not the output is to be separated.   */
/*                                     For example converting 65535 to base */
/*                                     16 would be FFFF if the output is not*/
/*                                     to be separated; otherwise it would  */
/*                                     output FF FF.                        */
/*    int     cSeparationCharacter;    If nSeparateOutput was defined as    */
/*                                     TRUE, then this is the character that*/
/*                                     would separate the groups.  For      */
/*                                     example, if cSeparationCharacter were*/
/*                                     "-", then the output would be FF-FF. */
/*    int     nPadToLeft;              This parameter determines whether or */
/*                                     not the output should be padded to   */
/*                                     the left with zero'
s.  For example,  */
/*                                     F FF would become 0F FF.             */
/*    int     *pcError;                   returns any errors                */
/*                                                                          */
/* Compilers: Microsoft 5.1                                                 */
/*            TurboC 2.0                                                    */
/*                                                                          */
/* Memory Model: any                                                        */
/*                                                                          */
/*                                                                          */
/* Switches: TEST - if == 1, a test driver is compiled                      */
/*           USE_FLOAT - if == 1, then conversion achieves greater          */
/*                       range by using floating point to hold the          */
/*                       intermediate results                               */
/*                                                                          */
/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */

#define     TEST            1   /* compile in test driver */
#define     USE_FLOAT       0   /* use floating point */


#if USE_FLOAT == 1
#define TEMP long double
#else
#define TEMP unsigned long
#endif

#include    <stdio.h>
#include    <stdlib.h>
#include    <string.h>
#include    <ctype.h>

#define     BYTE                        unsigned char
#define     WORD                        unsigned int
#define     NO_ERROR                    0
#define     ERROR                       !NO_ERROR
#define     INVALID_SOURCE_BASE         1
#define     INVALID_DESTINATION_BASE    2
#define     INVALID_CHAR_FOR_BASE       3
#define     INVALID_TOO_LARGE           4

#define     MAX_LENGTH_OUTPUT           80

/* RANGE OF BASES THE ROUTINE HANDLES */
#define  LOWER_LIMIT                              2
#define         UPPER_LIMIT                         36

int nBaseConvert(
        char    *pcNumberToConvert,
        int     nSourceBase,
        int     nDestinationBase,
        char    *pcResult,
        int     nSeparateOutput,
        int     cSeparationCharacter,
        int     nPadToLeft,
        int     *pnError)
{
        TEMP    TempValue1;
        TEMP    TempWidth;
        WORD    unTempValue2;
        WORD    unTemp;
        WORD    unLoop;
        char    acTempArray[2*MAX_LENGTH_OUTPUT + 1];
        char    *pcHolding;
        int     nReturnValue;

#if USE_FLOAT == 1
        WORD    wCounter;
        WORD    wNumberOfPlaces;
#endif

        /* ARRAY THAT DEFINES HOW WE GROUP OUTPUT.  Ex: bBatchArray[2] == 8,    */
        /* SO WE OUTPUT BASE 2 STUFF IN BLOCKS OF 8 DIGITS                      */
        static BYTE    bBatchArray[] =
                                   { 0, 0, 8, 0, 0, 0, 0, 0, 3, 0,
                                        3, 0, 0, 0, 0, 0, 2, 0, 0, 0,
                                         0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                                         0, 0, 0, 0, 0, 0, 0 };

        /* INITIALIZE THE ERROR FLAGS AND RETURN VALUES */
        *pnError = NO_ERROR;
        nReturnValue = NO_ERROR;
        *pcResult = '\0';

        /* BASES MUST BE BETWEEN LOWER_LIMIT AND UPPER_LIMIT */
        if( (nSourceBase < LOWER_LIMIT) || (nSourceBase > UPPER_LIMIT) )
        {
                *pnError  = INVALID_SOURCE_BASE;
                nReturnValue = ERROR;
        }
        else if( (nDestinationBase < LOWER_LIMIT) || (nDestinationBase > UPPER_LIMIT) )
        {
                *pnError  = INVALID_DESTINATION_BASE;
                nReturnValue = ERROR;
        }
        else
        {
                /* REVERSE ORDER - WE WORK FROM LEAST TO MOST SIGNIFICANT DIGIT */
                strrev( pcNumberToConvert );

                /* PUT BINARY pcNumberToConvert INTO unTempValue1 */
                for( unLoop=0, TempValue1=(TEMP) 0, TempWidth=(TEMP) 1;
                        pcNumberToConvert[unLoop];
                        unLoop++ )
                {

                        /* CONVERT CHARACTER TO NUMERIC EQUIVALENT */
                        unTempValue2 = UPPER_LIMIT;
                        if (isdigit(pcNumberToConvert[unLoop]))
                                unTempValue2 = pcNumberToConvert[unLoop] - '0';
                        else if (isalpha(pcNumberToConvert[unLoop]))
                                /* 'A' - '7' gives 10 ... */
                                unTempValue2 = toupper(pcNumberToConvert[unLoop]) - '7';

                        if( unTempValue2 >= nSourceBase )
                        {
                                *pnError  = INVALID_CHAR_FOR_BASE;
                                nReturnValue = ERROR;
                                break;
                        }
                        else
                        {
                                /* UPDATE TEMPORARY VALUE AND WIDTH */
                                TempValue1 += ((TEMP) unTempValue2) * TempWidth;
                                TempWidth *= (TEMP) nSourceBase;
                        }
                } /* end of for loop */

                /* RESTORE TO ORIGINAL ORDER */
                strrev( pcNumberToConvert );

                /* CHECK FOR POSSIBLE CONVERSION ERROR */
                if( *pnError == NO_ERROR )
                {

#if USE_FLOAT == 1
                        /* FIND HIGHEST DIVISOR */
                        for( TempWidth=(TEMP) 1, wNumberOfPlaces=1;
                                (TempWidth * (TEMP)nDestinationBase) <= TempValue1; )
                        {
                                TempWidth *= (TEMP)nDestinationBase;
                                wNumberOfPlaces++;
                        }

                        /* CONVERT TempValue1 TO DESTINATION BASE & PUT INTO acTempArray */
                        for( unLoop=0,wCounter=0; wNumberOfPlaces; unLoop++, wCounter++, wNumberOfPlaces-- )
                        {
                                unTemp = (WORD) (TempValue1 / TempWidth);
                                acTempArray[unLoop] = (char)(unTemp<10 ? unTemp+'0' : unTemp+'7' );
                                TempValue1 = TempValue1 - ((TEMP) unTemp * TempWidth );
                                TempWidth /= (TEMP) nDestinationBase;
                                if( unLoop >= MAX_LENGTH_OUTPUT - 1 )
                                {
                                        *pnError  = INVALID_TOO_LARGE;
                                        *acTempArray = '\0';
                                        return( ERROR );
                                }
                        }
                        /* NULL TERMINATE AND REVERSE THE RESULT STRING */
                        acTempArray[unLoop] = '\0';
                        strrev( acTempArray );

#else
                        /* CONVERT TempValue1 TO DESTINATION BASE & PUT INTO acTempArray */
                        unLoop=0;
                        while(TempValue1 != 0)
                        {
                                unTemp = (WORD) (TempValue1 % (TEMP) nDestinationBase);
                                TempValue1 /= (TEMP) nDestinationBase;
                                acTempArray[unLoop++] = (char)(unTemp<10 ?
                                        unTemp+'0' : unTemp+'7' );
                                if( unLoop >= MAX_LENGTH_OUTPUT - 1 )
                                {
                                        *pnError  = INVALID_TOO_LARGE;
                                        return( ERROR );
                                }
                        }
                        /* NULL TERMINATE THE RESULT STRING */
                        acTempArray[unLoop] = '\0';
#endif

                        /* DO ANY PADDING IF SPECIFIED */
                        if( nPadToLeft && bBatchArray[nDestinationBase] )
                        {
                                unLoop = (WORD) strlen( acTempArray );
                                while( (unLoop % bBatchArray[nDestinationBase]) )
                                        acTempArray[unLoop++] = '0';
                                acTempArray[unLoop] = '\0';
                        }

                        /* NOW GROUP VALUES IN "acTempArray" IF SPECIFIED */
                        if( nSeparateOutput && bBatchArray[nDestinationBase] )
                        {
                                pcHolding = acTempArray;
                                for( unLoop=1; *pcHolding; pcHolding++, unLoop++ )
                                {
                                        if( !(unLoop % bBatchArray[nDestinationBase]) )
                                        {
                                                memmove( pcHolding+2, pcHolding+1, strlen( pcHolding+1 ) + 1 );
                                                *++pcHolding = (char) cSeparationCharacter;
                                                unLoop = 0;
                                        }
                                }
                                if( *--pcHolding == (char) cSeparationCharacter )
                                        *pcHolding = '\0';
                        }
                        if( strlen(acTempArray) >= MAX_LENGTH_OUTPUT )
                        {
                                *pnError  = INVALID_TOO_LARGE;
                                nReturnValue = ERROR;
                        }
                        else
                        {
                                strcpy( pcResult, acTempArray );
                                strrev( pcResult );
                        }

                } /* END OF IF NO_ERROR */
        } /* END OF ELSE NO_ERROR */

        return( nReturnValue );
}


#if TEST==1
int main( int argc, char **argv)
{
        char    acResult[80]; /* holds result of conversion       */
        int     nErrorCode; /* holds error code         */
        char    *pcPointer;

        if( argc != 6 && argc != 7 )
        {
                printf( "BASECVT - convert a number from one base to another for bases 2 - 36\n" );
                printf(" FORMAT:\nbasecvt number src_base dest_base separate fill_out sep_char\n" );
                printf("         number      - the number to be converted\n");
                printf("         src_base    - the current base of 'number'\n");
                printf("         dest_base   - the desired destination base\n");
                printf("         separate    - 1 = print result in groupings (e.g. FF FF)\n");
                printf("                       else 0 = do not group (e.g., FFFF)\n");
                printf("         fill_out    - 1=pad with leading '0', else 0=no pad\n");
                printf("         sep_char    - (optional): charcter that separates groups of digits.\n");
                printf("                       The default is ' '.\n");
                return( 1 );
        }
        nBaseConvert( argv[1], atoi(argv[2]), atoi(argv[3]), acResult,
                atoi(argv[4]),
                argc == 6 ? ' ' : *argv[6],
                atoi(argv[5]), &nErrorCode );

        pcPointer = NULL;

        switch( nErrorCode )
        {
                case    INVALID_SOURCE_BASE:
                        pcPointer = "Invalid source base";
                        break;
                case    INVALID_DESTINATION_BASE:
                        pcPointer = "Invalid destination base";
                        break;
                case    INVALID_CHAR_FOR_BASE:
                        pcPointer = "Invalid character for source base";
                        break;
                case    INVALID_TOO_LARGE:
                        pcPointer = "Invalid result too large";
                        break;
        }

        if( pcPointer == NULL )
                printf( "Result = %s\n", acResult );
        else
                printf( "%s\n", pcPointer );
}
#endif
nTempValue1 */
                for( unLoop=0, TempValue1=(TEMP) 0, TempWidth=(TEMP) 1;
                        pcNumberToC

corner
© 1996-2008 CommunityHeaven LLC. All rights reserved. Reproduction in whole or in part, in any form or medium without express written permission is prohibited.
Violators of this policy may be subject to legal action. Please read our Terms Of Use and Privacy Statement for more information.
North American business development: Nicolai Wadstrom. Publisher: Lars Hagelin.