1

I'm trying to write a program that takes input of - hexadecimals, octals, and decimals -, stores them in integer variables, and outputs them along with their conversion to decimal form. For example:

User inputs: 0x43, 0123, 65

Program outputs:

0x43 hexadecimal converts to 67 decimal
0123 octal converts to 83 decimal
65 decimal converts to 65 decimal

So obviously I need a way to interpret the numbers, but I'm not sure how to go about doing it. I've tried various methods such as reading them into a function and converting them into a string, and vice versa (see here for code examples), but interpreting the numbers always requires conversion to some format that trashes the original input.

The only thing I can think of is overloading a >> operator that reads a character at a time and if it sees 0x or 0 at the beginning of the input then it stores the whole input into a string before it is read into an int. Then the program would somehow have to determine the right manipulator during output.

Not sure if there is a simpler way to do this, any help is appreciated.

Edit: This has been solved, but I decided to post the code in if anyone is interested.

#include "std_lib_facilities.h"

void number_sys(string num, string& s)
{
  if(num[0] == '0' && (num[1] != 'x' && num[1] != 'X')) s = "octal";
  else if(num[0] == '0' && (num[1] == 'x' || num[1] == 'X')) s = "hexadecimal";
  else s = "decimal";
}

int main()
{
    cout << "Input numbers in hex, dec, or oct. Use 0xx to cancel.\n";
    string a;

    while(cin >> a){
    if(a == "0xx")break;
    string atype;
    number_sys(a, atype);

    int anum = strtol(a.c_str(), NULL, 0);

    cout << a << setw(20-a.length()) << atype << setw(20) << "converts to" << setw(10)
         << anum << setw(10) << "decimal\n";
                 }

    keep_window_open();
}
ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
trikker
  • 2,651
  • 10
  • 41
  • 57
  • 1
    Is this a learning exercise or do you just want to call atoi()? – Michael Burr Jul 28 '09 at 18:44
  • 1
    retagged as c, since this is a simple C problem and is not specific to C++. – Dave DeLong Jul 28 '09 at 18:49
  • 1
    atoi() doesn't work. It would read 0x43 as a string to 43 as an integer, which is incorrect, I'm implementing Dave DeLong's solution right now because strtol takes base into consideration. – trikker Jul 28 '09 at 18:53
  • @tikker: but scanf does if you use the %i specifier. Michael's question is relevant though: to just get it working, use scanf, to learn somthing, roll your own. – dmckee --- ex-moderator kitten Jul 28 '09 at 19:13
  • Oops - sorry about the atoi() mixup - I was thinking about strtol(), but my brain ended up at atoi(). But I was really wondering if you just wanted a simple, canned solution or if you wanted guidance on rolling your own. – Michael Burr Jul 28 '09 at 20:17
  • 1
    If there are tools available to me to solve the problem I use them. No point in trying to unscrew a screw with a hammer when you have a screwdriver right there. Plus it just builds upon my knowledge of the language when I learn these new tools. Overloading the input operator and playing with the input stream is just a way to give yourself a headache. Only if absolutely needed :P – trikker Jul 28 '09 at 21:27
  • 1
    Using the right tool is good. But understanding bases, conversion and string/number representations are core things to understand well. Might be worth rolling your own once for the knowledge. I'd do it in plain C and skip any operator overloading while learning about this stuff. – Steve Fallows Jul 28 '09 at 22:05
  • I'm using a book called Programming: Principles and Practice Using C++ and there's a chapter at the end devoted to C so I'll get there. I spent awhile fully understanding hexadecimals, octals, decimals, and binary, and the differences between them and the conversions between them (I wrote some conversion problems to solve for myself.) The book also covers how strings and ints are represented, it just didn't clarify on the interpretation when dealing with them in input (besides manipulators). I've written custom input in other exercises and I've learned that input specifically is very – trikker Jul 28 '09 at 22:44
  • error prone, so processing after input, but before output is generally preferred to processing right there in the input stream. – trikker Jul 28 '09 at 22:45
  • In your first if statement, your comparison is wrong. You have an AND operator where you want an OR. – jkeys Jul 29 '09 at 00:22
  • 1
    No...it's right. if the first element is 0 and element 1 is not x and is not X then it is an octal. – trikker Jul 29 '09 at 00:43

5 Answers5

11

Take a look at the strtol function.

char * args[3] = {"0x43", "0123", "65"};
for (int i = 0; i < 3; ++i) {
  long int value = strtol(args[i], NULL, 0);
  printf("%s converts to %d decimal\n", args[i], value);
}

Outputs:

0x43 converts to 67 decimal
0123 converts to 83 decimal
65 converts to 65 decimal
Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
  • @Welbog *shrug* it's something that can be easily found by googling. I had to find this exact function a couple of weeks ago for a homework assignment. – Dave DeLong Jul 28 '09 at 18:54
  • This isn't homework. If you read my profile I'm self learning from a book. – trikker Jul 28 '09 at 18:54
  • Oh. Well, you should remove the homework tag. That really skews how a lot of us answer these things. – Welbog Jul 28 '09 at 18:55
  • That doesn't mean you can't get rid of it. – Welbog Jul 28 '09 at 18:56
  • Having a hard time implementing this since I haven't learned about pointers yet. Plus I'm using string variables rather than inputting the actual strings in the char * args[3] – trikker Jul 28 '09 at 19:09
  • This worked, implemented it a bit differently but it worked! Thanks! (Using c_str(), null, 0, as arguments) – trikker Jul 28 '09 at 19:23
  • 1
    @Janie that's cool. It's an exercise for programming, I could write down "Hello World" on a piece of paper much easier than I could program it as well (or could I :P) – trikker Jul 28 '09 at 22:47
1

You could always store it as a string to start, and look at the first two characters to see if they are 0x:

std::string num;
std::cin >> num;

if (num[0] == '0' && num[1] == 'x')
{ 
  //handle
}
jkeys
  • 3,803
  • 11
  • 39
  • 63
  • C standard library functions (like strtol) are a better approach. – Quinn Taylor Jul 28 '09 at 18:53
  • @Quinn: Since strtol doesn't tell you what base the number was in, and the OP wants to print "octal", "hexadecimal", "decimal" in the output, some manual base-determination of this kind is necessary even if you're using strtol to do the conversion. So "handle" should mean 'print "hexadecimal" and then call strtol'. – Steve Jessop Jul 28 '09 at 19:00
  • @onebyone, the printing function for the base type has already been handled. I just needed help with the conversion. – trikker Jul 28 '09 at 19:01
  • @hooked, that still doesn't deal with the conversion from string to int, it just reads the string which I've already done. – trikker Jul 28 '09 at 19:11
1

I'm not sure if there is a C++ way of doing this, but if you don't mind a little C-ishness, you can read the thing into a char array and use something like sscanf(buffer, "%i", &output). The %i interprets the input as hex, octal or decimal depending on its format, just like you describe.

Edit: Ah, didn't know that strtol could also do this. Ignore me.

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • 1
    C standard library functions (like strtol) are a better approach. – Quinn Taylor Jul 28 '09 at 18:54
  • I agree that `strtol` is better than `sscanf`, but `sscanf` is also a C standard library function. The format specifier `%i` is quite standard too. – Thomas Jul 28 '09 at 19:17
1

If you want to preserve the base information (hex/oct/dec), you will need to store that information separately from the integer value itself, and it will require you to parse at least the first couple of characters of the input string (sscanf(), strtol(), etc., won't preserve that information for you).

You could roll your own mini-parser that saves the input base and does the conversion (code off the top of my head, untested):

char inputStr[MAX_INPUT_LENGTH+1];
char *p;
int result = 0;
char values[128];

/**
 * This enumeration serves double duty; it keeps track of what
 * base the input was entered in, and it controls the state machine 
 * used to parse the input; from a didactic POV, this is probably bad form
 */
enum {
  eStart, 
  eHexOrOctal, 
  eOctal, 
  eDecimal, 
  eHexadecimal, 
  eError
} eBase = eStart;


/**
 * Use the values array as a table to map character constants to their corresponding 
 * integer values.  This is safer than using an expression like *p - '0', in 
 * that it can work with character encodings where digits are not consecutive.
 * Yes, this wastes a little space, but the convenience makes
 * up for it IMO.  There are probably better ways to do this.
 */
values['0'] = 0; values['1'] = 1; values['2'] = 2; values['3'] = 3; 
values['4'] = 4; values['5'] = 5; values['6'] = 6; values['7'] = 7; 
values['8'] = 8; values['9'] = 9; values['a'] = 10; values['b'] = 11; 
values['c'] = 12; values['d'] = 13; values['e'] = 14; values['f'] = 15;

/**  
 * Insert code to get input string here 
 */

for (p = inputStr; *p != 0; p++)
{
  /**
   * Cycle through each character in the input string, adjusting the state
   * of the parser as necessary.  Parser starts in the eStart state.
   */
  switch(eBase)
  {
    /**
     * Start state -- we haven't parsed any characters yet
     */
    case eStart:
      if (*p == '0') eBase = eHexOrOctal; // leading 0 means either hex or octal
      else if (isdigit(*p))
      {
        eBase = eDecimal;    // leading non-0 digit means decimal
        result = values[*p];  
      }                    
      else eBase = eError;    // no other character may start an integer constant
      break;
    /**
     * HexOrOctal -- we've read a leading 0, which could start either a hex or
     * octal constant; we need to read the second character to make a determination
     */
    case eHexOrOctal:      
      if (tolower(*p) == 'x')  base = eHexadecimal;
      else if (isdigit(*p) && *p != '8' && *p != '9')
      {
        base = eOctal;   
        result = values[*p];
      }
      else eBase = eError;
      break;
    /**
     * Octal -- we are parsing an octal constant
     */
    case eOctal:
      if (isdigit(*p) && *p != '8' && *p != '9')
      {
        result *= 8;
        result += values[*p];
      }
      else eBase = eError;
      break;
    /**
     * Decimal -- we are parsing a decimal constant
     */
    case eDecimal:
      if (isdigit(*p))
      {
        result *= 10;
        result += values[*p];
      }
      else eBase = eError;
      break;
    /**
     * Hexadecimal -- we are parsing a hex constant
     */
    case eHexadecimal:
      if (isxdigit(*p))
      {
        result *= 16;
        result += values[tolower(*p)];
      }
      else eBase = eError;
      break;
    /**
     * String is not a properly formatted integer constant in 
     * any base; once we fall into the error state, we stay there.
     */
    case eError:
    default:
      break;
  }
}
if (eBase != eError)
{
  printf("input: %s ", inputStr); fflush(stdout);
  switch(eBase)
  {
    case eOctal: printf("octal "); break;
    case eHexadecimal: printf("hexadecimal "); break
    default: break;
  }
  fflush(stdout);
  printf("converts to %d decimal\n", result);
}
else
{
  /** Print a suitable error message here */
}
John Bode
  • 119,563
  • 19
  • 122
  • 198
0

If you literally have to use a single integer variable for storage of all the information that you need in order to display your final output, then you have to use part of the integer variable for storing the original base that the input was in. Otherwise it's not recoverable.

chaos
  • 122,029
  • 33
  • 303
  • 309