#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