Search code examples
c++streamhexnegative-integer

c++ stream negative number conversion


I came across an issue with C++ trying to read a text file filled with signed integer numbers in hexadecimal form and parsing them to vectors. I used the C++ stream to variable redirect (stream >> var), and it seems that negative numbers are not parsed correctly - the variable gets the value 0, and the stream fail flag is set.

If I try to convert the string using strtol() function, the results are as expected. Likewise, if I try to first redirect the stream to an unsigned integer and than cast the variable to signed integer, the results are again correct and no stream error is reported.

I'm using gcc 6.3.0 on Debian 9.1 (x64), running on Xeon E5-2643 v3 system.

Did anyone else experience this issue? I would expect the conversion to work the same way as the strtol function, and not report any stream errors. Am I missing some stream settings / forgetting to call some function or set some flag here?

Any suggestions would be greatly appreciated.

Attached below is an example C++ program demonstrating this problem.

#include <iostream>
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cstdint>


int main()
{
  const char* minus_one = "0xffffffff";

  std::stringstream ss;
  ss << minus_one;

  std::cout << "input string    : " << ss.str() << "\n"; // outputs "0xffffffff"

  // C-style conversion
  int32_t cint;
  cint = strtol(ss.str().c_str(), NULL, 0);
  std::cout << "strtol conv     : " << cint <<  " (" << std::hex << cint << ")\n"; // outputs "-1 (ffffffff)"
  std::cout << std::dec;

  // C++-style conversion
  int32_t cppint;
  ss >> std::hex >> cppint;
  std::cout << std::dec << "ssextr conv     : " << cppint <<  " (" << std::hex << cppint << ")\n"; // outputs "0 (0)" <- ERROR
  std::cout << std::dec;
  if (ss.fail()) std::cout << "Error converting number.\n";

  // C++-style conversion with cast
  uint32_t cppuint;
  int32_t cppint2;
  ss.clear();
  ss.str(minus_one);
  ss >> std::hex >> cppuint;
  cppint2 = (int32_t)cppuint;
  std::cout << std::dec << "ssextr cast conv: " << cppint2 <<  " (" << std::hex << cppint2 << ")\n"; // outputs "-1 (0xffffffff)"
  std::cout << std::dec;
  if (ss.fail()) std::cout << "Error converting number.\n";

  exit(EXIT_SUCCESS);
}

Solution

  • int32_t cint;
    cint = strtol(ss.str().c_str(), NULL, 0);
    

    This reads the value 0xffffffff into a long, then converts that to int32_t. If long is larger than 32-bits the strtol works and returns 0xffffffff i.e. 4294967295, and converting that to int32_t produces -1. But that's not the same as reading a negative number from the string (and if long is 32-bits then it doesn't work as you expect, instead it returns LONG_MAX and converts that to int32_t, which is 0x7fffffff).

    int32_t cppint;
    ss >> std::hex >> cppint;
    

    This tries to read the value 0xffffffff into an int32_t but the value 0xffffffff doesn't fit in that type, so reading the value fails (just like it fails with strtol when long is 32-bits).

    A closer equivalent to your strtol version would be:

    int32_t cppint;
    long l;
    if (ss >> std::hex >> l)
      cppint = l;
    else
      // handle error ...
    

    It's unreasonable to expect to be able to read the value 0xffffffff into a signed 32-bit integer. strtol and istreams do not read bit patterns, they read numbers, and the number 0xffffffff doesn't fit in a signed 32-bit integer.