|[ LiB ]|
I'm going to come right out and say this: 32-bit integers are getting too small. Being able to store from -2 billion to +2 billion numbers, 32-bit numbers seemed big a long time ago, but that's just not enough storage space for numbers anymore.
Consider this: The standard C++ second-timer uses a signed 32-bit integer to store the number of seconds that has passed since 1970. What happens when you get past 2 billion seconds? Simple: Integers wrap around and become -2 billion instead. Obviously, this is a problem, but how long is 2 billion seconds anyway? It's 35 million minutes, or 596 thousand hours, which is 24 thousand days. That's 68 years . In 2038, all of our C++ second-timers will wrap around. That's a big problem.
As game programmers, we may not have to be concerned with wrapping second-timers; instead, we'll probably be using wrapping millisecond-timers. So how many milliseconds can 32 bits represent? About 24 days. Since this book is about programming persistent worlds with huge uptimes , 24 days just won't cut it for us.
So, why don't we use 64-bit numbers instead? A signed 64-bit number can hold from -9 quintillion to +9 quintillion. That's a huge number; in fact it's so large that you probably can't conceive how big it actually is. To put things is perspective, when used to store milliseconds, 64-bit integers can represent 292 million years. I think 64 bits ought to be enough for a timer.
So, the first thing you need to do is create a uniform 64-bit integer format. I'm going to do this within the BasicLib library package, which you can find on the CD in the directory /Libraries/BasicLib/. The file that will hold the 64-bit datatype is called BasicLibTypes.h. Everything within this file is also within the BasicLib namespace.
In the C99 standard, C is supposed to have a long long datatype (two longs), which is 64 bits. For example:
long long foo; // signed 64 bits unsigned long long bar; // unsigned 64 bits
Unfortunately, not all compilers are up-to-date. The Linux compiler, GCC, has no problems with 64-bit integers, but Microsoft Visual C++.NET and prior versions don't support it (VS.NET 2003 should support it, though); therefore, you need to use the Microsoft-specific 64-bit integer format:
__int64 foo; // signed 64 bits unsigned __int64 bar; // unsigned 64 bits
Personally, I never use bitshifts any more anyway. I put my trust into the compiler to make the best decision about optimizing my code, since it can do a much better job than I can.
In front of the int64 are two underscores, not just one. As far as I can tell, __int64 acts just like a long long , with just one exception: it doesn't support bitshifting. I'm not sure why this is, but it's one limitation you must keep in mind when using __int64 .
So, finally, here's the code that seamlessly uses __int64 's or long long s based on the system you are using:
#ifdef __GNUC__ // Linux typedef long long int sint64; typedef unsigned long long int uint64; #endif #ifdef WIN32 // Windows typedef __int64 sint64; typedef unsigned __int64 uint64; #endif
I've typedefed the 64-bit integers into new types: sint64 and uint64 . Now, whenever you need to use a 64-bit number, you can just include the BasicLib.h file, and use it:
#include "BasicLib/BasicLib.h" sint64 foo; uint64 bar;
Voil ! Platform-independent 64-bit integers!
64-Bit Integers and Streams
Visual C++ 6 has a major problem streaming 64-bit integers to and from streams. I think it's actually a bug somewhere in the template code, but regardless, you just can't do it. Since Microsoft has already created a newer and better compiler, I can't imagine it cares much about fixing the old version. You're going to have to live with this limitation.
I worked around the limitation and created a hack . Yes, hacks are an ugly thing, but sometimes they are just plain neccessary.
To cut a long story short, I had to create some way of determining if you're using VC6, and I did so using macros:
#ifdef WIN32 #if _MSC_VER >= 1300 #define GOODCOMPILER #else #define CRAPPYCOMPILER #endif #endif #ifdef __GNUC__ #define GOODCOMPILER #endif
If you have VC7 or above, or GCC, you have a good compiler. If you have VC6 or below, you have a crappy compiler. To work around the streaming problem, I created a few stream helper functions:
template< typename type > inline void insert( std::ostream& s, const type& t ); template< typename type > inline type& extract( std::istream& s, type& t );
These functions essentially perform the same task as the common operator<< and operator>> (see Appendix C, "C++ Primer," on the CD if you are unfamiliar with them), but since those operators are broken in VC6, you need to use these instead whenever you stream a sint64 or uint64 . For example:
sint64 bigint = 12345677889467365; // cout << bigint << endl; <-- WILL NOT COMPILE ON VC6 BasicLib::insert( cout, bigint ); BasicLib::extract( cin, bigint );
You can find these functions in the BasicLibString.h file on the CD; I had to create a template specialization for the 64-bit integer types, which wraps around the VC6 _i64toa , _ui64toa , and _atoi64 functions. A template specialization is a function that works on a specific kind of datatype. It's a rather large and complex topic, so I won't be covering it here. All you really need to know is that you need to use insert and extract when streaming 64-bit integers.
|[ LiB ]|