John Harrison wrote:
Ok, let me think this through carefully. I just feel in my bones that
something is wrong with the line
if (A <= MAX_A && n < static_cast<ptrdiff_t>(-A) ||
as a means of checking whether n below [A,0).
We know A is unsigned and
A <= MAX_A
where MAX_A ==
1+static_cast<size_t>(std::numeric_limits<ptrdiff_t>::max())
i.e.
A in [0,MAX_A]
Thus -A in [2^N-1,2^N-MAX_A] or -A == 0.
Now, what happens if we cast -A to the signed type? For values of -A
above numeric_limits<ptrdiff_t> (and that is almost all values that occur
here), we have an implementation defined value [4.7/3].
This is probably bad.
Best
Kai-Uwe Bux
Oh well, back to the drawing board. Why can't the standard just say that
result of signed/unsigned comparisons is the mathematically correct one?
Now, that would take all the fun out of the built-in types, wouldn't it?
As far as I can tell, the standard make the following choice: the signed
types are supposed to be the machines native types. Accordingly, there are
very few guarantees about what they do. Being cynical, once coud say that
the best thing about them, is that it is mathematically defined how they
convert to the unsigned types. The unsigned types make string guarantees:
they implement arithmetic mod 2^N (N is the bitlength). This goes even for
multiplication. Thus unsigned arithmetic is perfectly well-defined.
The standard then had to decide what to do about mixed expressions. The
choice was made that in this case, predictability comes before performance.
The way to ensure predictability is to cast all operands in a mixed binary
expression to unsigned. You have a point with regard to the comparison
operators: it is rather un-natural.
My way to deal with this, is to test first whether the signed value is
negative. Then, the very next step has to be a cast. From then on,
everything is well-defined.
BTW: now, I think, I got a somewhat way of doing the problem. The code
reflects the natural order of the ranges, and uses only one cast. It may
trigger tons of warnings, though, about mixed comparisons.
#include <iostream>
#include <limits>
int main ( void ) {
unsigned long A = 30;
unsigned long B = 20;
unsigned long C = 100;
int x = 0;
while ( std::cin >> x ) {
if ( x < 0 ) {
// we cast first so that unary - does not cause an overflow:
if ( -static_cast<unsigned long>(x) > A ) {
// underflow: treated like overflow!
goto out_of_range;
} else {
std::cout << "range 1";
}
} else {
if ( x < B ) {
std::cout << "range 2";
} else if ( x < C ) {
std::cout << "range 3";
} else {
// overflow:
out_of_range:
std::cout << "out of range";
}
}
std::cout << '\n';
}
}
(You may consider the goto a hook for the maintenace programmer should the
need arise in the future to distinguish between overflow and underflow
Best
Kai-Uwe Bux