inline char to_char( const int x )The function's name does not hint at the hexadecimal notation, and that's not without reason. When I use the hexadecimal notation, it often is when programming hardware registers whose bits represent operations such as select ADC number x, or clear FIFO. It can be helpful to see the value of those bits separately, that is in binary notation. It may well be that we can present the numbers in hexadecimal (base 16) as well as binary (base 2) notation with very little extra effort.
{
return static_cast<char>(
x <= 9 ? '0' + x : 'A'-10 + x );
}
On my Palm Zire 72 I use Ding Zhaojie's Megatops BinCalc[3] . It has a nice equal opportunity[4] user interface for the binary input-output. |
Presented an example how easy visual
programming is, I can't help
to ask: Ah, can I also grab and move the needle and read the
corresponding decimal value on the knob? But I digress... |
#include <string> // std::stringNote that the program accepts the base to present the number in as the program's first argument. It also contains the try-catch skeleton to handle errors. Implementation of various other ideas to make the program 'more wonderful' are left to the reader:
#include <iostream> // std::cout, cerr
int to_int( std::string text )
{
return atoi( text.c_str() );
}
int main( int argc, char* argv[] )
{
const int default_base = 16;
try
{
const int base =
(argc > 1) ? to_int( argv[1] )
: default_base;
report( std::cout, "cout",
read( std::cin , "cin" ), base );
}
catch ( std::exception const& e )
{
std::cerr << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
const std::string progname = "decToBase";The new prototype of the conversion function is:
int read( std::istream& is, std::string name )
{
int integer = 0;
is >> integer;
if ( !is )
{
throw std::runtime_error(
progname +
": cannot read number from stream '" +
name + "'" );
}
return integer;
}
void report( std::ostream& os, std::string name,
const int x, const int base )
{
os <<
x <<" in base '" << base << "' "
"is '" << to_string( x, base ) << "'" <<
std::endl;
if ( !os )
{
throw std::runtime_error(
progname +
": cannot not write to stream '" +
name + "'" );
}
}
std::string to_string( const int x,What I like about the converter's original implementation is its use of std::stack, well actually I like its use of a container from the C++ standard library. In the following implementation, I've continued this approach.
const int base );
std::string to_string( const int x,Indeed, std::string is a container, be it a rather awkward one. Kevlin Henney wrote several interesting articles on the container aspect of std::string[7]. He also argues that using high-level (generic) programming constructs are part of a modern approach to teaching C++[8].
const int base = 10 )
{
REQUIRE( base >= 2);
REQUIRE( base <= 36 );
unsigned int ux( x );
unsigned int ubase( base );
std::string result;
do
{
result.insert(
0, // position
1, // count
to_char( ux % ubase )
);
}
while( ( ux /= ubase ) > 0 );
return result;
}
std::string result( 0 == ux ? "0" : "" );And here are the results:
for( ; ux > 0; ux /= base )
{
result.insert(
0, // position
1, // count
to_char( ux % base )
);
}
prompt>decToBase.exeInitially, before I revisited std::string and found a usable insert method, I worked out another solution that I like to share.
123
123 in base '16' is '7B'
prompt>decToBase.exe 2
123
123 in base '2' is '1111011'
prompt>decToBase.exe
-1
-1 in base '16' is 'FFFFFFFF'
#include <iterator> // std::inserterThe std::stack container is replaced with the more general std::vector. As a result, the std::copy algorithm can be used to insert the collected figures in a std::string in reverse order, by employing reverse iterators on the vector named reversed. The idea to condense the copy operation as shown is taken from Software As Read, by Jon Jagger[10].
#include <vector> // std::vector<>
#include <string> // std::string
template< typename range, typename output >
output rcopy( range const& source, output sink )
{
return std::copy(
source.rbegin(), source.rend(), sink );
}
std::string to_string( const int x, const int base = 10 )
{
REQUIRE( base >= 2 );
REQUIRE( base <= 36 );
unsigned int ux( x );
unsigned int ubase( base );
std::vector<char> reversed;
do
{
reversed.push_back( to_char( ux % ubase ) );
}
while( ( ux /= ubase ) > 0 );
std::string result;
rcopy( reversed, std::back_inserter( result ) );
return result;
}
#include <iomanip> // std::hexYet another uses the C function _ltoa().
#include <sstream> // std::stringstream
std::string to_hex( const int x )
{
std::stringstream oss;
oss << std::hex << x;
return oss.str();
}
std::string to_string( const int x, const int base )What are your expectations? Recall the exception handling in main() and imagine you enter a base on the commandline that is outside the valid range of 2-36 and request a conversion. The conversion function receives a base that it cannot handle in a constructive way. This is a situation that should not happen and thus it is a programming error[11]. Here is macro REQUIRE's job: throw an exception if the stated requirements, or preconditions[12] base >= 2 and base <= 36 are not true, as it occurs in[13]:
{
REQUIRE( base >= 2 );
REQUIRE( base <= 36 );
char buf[ 8 * sizeof( x ) + 1 ];
return _ltoa( x, buf, base );
}
prompt>decToBase.exe 1Note that a user entering an invalid base in fact represents an environmental error, that is, an error that is not unexpected to happen. It is the program that should prevent the invalid base to reach the conversion function that is known to be unable to handle it[14]. Contrast this with a programming error that leads to the same situation: that is not expected to happen.
123
decToBase.cpp(60): expected 'base >= 2'
#include <stdexcept> // std::runtime_exceptionMacro REQUIRE uses two conversion shims[15], one of which is the already familiar number-to-string converter. The story recurses.
#define REQUIRE( expr ) \
if ( !(expr) ) \
{ \
throw std::runtime_error( \
to_string(__FILE__) + \
"(" + to_string(__LINE__) + \
"): expected '" + #expr + "'" ); \
}
std::string to_string( std::string text )The choice of the parameter type of the first to_string() may come as a surprise. However, with std::string as its parameter type, the function can also be used with arguments that are convertible to that type, such as __FILE__'s type char*.
{
return text;
}
std::string to_string( const int x
/*, const int base = 10 */ )
{
// guess what
}
std::cin >> integer;Especially in this context I like the word integer.
Martin Moene