Weary of using -1 for max unsigned values

M

Martin Wells

I commonly use the likes of -1 for max unsigned values, or something
like -7 to get 6 away from the max value; sort of like:

unsigned x = ...

if (-7 == x) puts("x is 6 away from its maximum");

Since I rarely use anything smaller than int, this has never been a
problem. Obviously though, there'd be a problem if I tried:

char unsigned x = ...

if (-7 == x) puts("x is 6 away...

The reason there'd be a problem is that x could be promoted to a
SIGNED int.

So I'm wondering... what's the handiest way of checking this for any
unsigned integer expression?

In the case of unsigned char, we can do
if ((char unsigned)-7 == x)

Similarly in the case of unsigned short, we can do
if ((short unsigned)-7) == x)

But what I'm looking for is a universal method. My first thoughts were
something like

#define MAX(expr) ( (expr) * (char_unsigned)0 - (char
unsigned)1 )

The problem with this of course though is that, even though we have
the casts, everything will still get promoted to int (either signed or
unsigned depending on INT_MAX).

Anyone got any ideas such that I can write universal code such as?:

if (MAX(x)-6 == x) puts("x is 6 away from its maximum");

If we had a typeof operator it would probably be something like:

#define MAX(expr) ( (typeof expr)-1 )

I have the likes of "-1 == x" littered throughout my code... and yes
it works fine... but I'd like something more sustainable just in case
I'd choose to use something smaller than an int.

Martin
 
J

jameskuyper

Martin said:
I commonly use the likes of -1 for max unsigned values, or something
like -7 to get 6 away from the max value; sort of like:

unsigned x = ...

if (-7 == x) puts("x is 6 away from its maximum");

Since I rarely use anything smaller than int, this has never been a
problem. Obviously though, there'd be a problem if I tried:

char unsigned x = ...

if (-7 == x) puts("x is 6 away...

The reason there'd be a problem is that x could be promoted to a
SIGNED int.

So I'm wondering... what's the handiest way of checking this for any
unsigned integer expression?

if (UINT_MAX-6 == x ) ...

Replace UINT_MAX with UCHAR_MAX, USHRT_MAX, ULONG_MAX, ULLONG_MAX, or
SIZE_MAX, as appropriate.
 
M

Martin Wells

james:
if (UINT_MAX-6 == x ) ...

Replace UINT_MAX with UCHAR_MAX, USHRT_MAX, ULONG_MAX, ULLONG_MAX, or
SIZE_MAX, as appropriate


Using TYPE_MAX is no more universal than (TYPE)-1. I'm looking for
something I can use with any unsigned integer type.

Martin
 
V

vipvipvipvipvip.ru

Martin Wells you come up with the silliest problems.
first memset, now this..
 
M

Mark McIntyre

james:



Using TYPE_MAX is no more universal than (TYPE)-1. I'm looking for
something I can use with any unsigned integer type.

Seems to me you want a template type.
Unfortunately C++ is thattaway ---->>


BTW I can't say I've ever encountered any actual real-world example of
your problem, so perhaps this is a solution looking for a problem...
..
--
Mark McIntyre

"Debugging is twice as hard as writing the code in the first place.
Therefore, if you write the code as cleverly as possible, you are,
by definition, not smart enough to debug it."
--Brian Kernighan
 
C

Charlie Gordon

Martin Wells said:
james:



Using TYPE_MAX is no more universal than (TYPE)-1. I'm looking for
something I can use with any unsigned integer type.

Here is one:

#define MWELLS_CMP(val, x) \
(((unsigned long long)(long long)(val) & \
(-1ULL >> ((sizeof(1ULL) - sizeof(x)) * CHAR_BIT)) == (x))

Use it as:

if (MWELLS_CMP(-6, x)) ...

It should work for any x with unsigned type.
Both val and x are evaluated once in the macro expansion.
A smart compiler should generate efficient code for types smaller than
unsigned long long, but it might not.
 
M

Martin Wells

Chqrlie:
#define MWELLS_CMP(val, x) \
(((unsigned long long)(long long)(val) & \
(-1ULL >> ((sizeof(1ULL) - sizeof(x)) * CHAR_BIT)) == (x))

Use it as:

if (MWELLS_CMP(-6, x)) ...


(You've got an extra open parenthesis at the very start)

I'm gonna think out loud here and see if I understand it

(

(unsigned long long)(long long)(val)

Here you take val, convert it to a long long, then to an unsigned long
long. Why don't you go straight to an unsigned long long?

&
(
-1ULL >>
( (sizeof(1ULL) - sizeof(x)) * CHAR_BIT )
)

== (x)
)

Now you take the byte difference between unsigned long long and the
expression, and multiply it by CHAR_BIT to get the bit difference. You
shift the max value of unsigned long long to the right by this bit
difference. You then compare this value to X, yielding an int value of
either 1 or 0, and you then bitwiseAND this with the previous value
above. I haven't fully thought it through but it seems you might have
an error above as regards operands and operator precedence. Anyway,
two flaws that stick out are:

1) Unsigned types can have padding, so sizeof isn't reliable for the
amount of value representational bits.
2) Use of unsigned long long is specific to C99. If we're gonna bother
with C99, then we'd be better off to go with int_max_t or whatever
it's called. As for C89, we can only go with unsigned long... but the
implemenation could provide a bigger type.

Martin
 
J

James Kuyper

Dan said:
-1 does not convert to the maximum unsigned value for one's complement
representation.

The representation of the signed integer type is irrelevant. It's the
value that gets converted, not it's representation. The relevant rule is
given in 6.3.1.3p2:

"Otherwise, if the new type is unsigned, the value is converted by
repeatedly adding or subtracting one more than the maximum value that
can be represented in the new type until the value is in the range of
the new type."

In the case of -1, that means adding "one more than the maximum value"
to -1, which gives you exactly the maximum value.
 
C

Charlie Gordon

James Kuyper said:
The representation of the signed integer type is irrelevant. It's the
value that gets converted, not it's representation. The relevant rule is
given in 6.3.1.3p2:

"Otherwise, if the new type is unsigned, the value is converted by
repeatedly adding or subtracting one more than the maximum value that can
be represented in the new type until the value is in the range of the new
type."

In the case of -1, that means adding "one more than the maximum value"
to -1, which gives you exactly the maximum value.

The likely consequence of this is that casting a int to unsigned on one of
those obsolete museum pieces emits code, possibly even a test and one or two
jumps.

One's complement is nothing to worry about.
 
C

Charlie Gordon

Martin Wells said:
Chqrlie:



(You've got an extra open parenthesis at the very start)

Actually, there is a missing parenthesis after CHAR_BIT.

#define MWELLS_CMP(val, x) \
(((unsigned long long)(long long)(val) & \
(-1ULL >> ((sizeof(1ULL) - sizeof(x)) * CHAR_BIT))) == (x))
I'm gonna think out loud here and see if I understand it

(
(unsigned long long)(long long)(val)

Here you take val, convert it to a long long, then to an unsigned long
long. Why don't you go straight to an unsigned long long?

no good reason, let's simplify:

#define MWELLS_CMP(val, x) \
(((unsigned long long)(val) & \
(-1ULL >> ((sizeof(1ULL) - sizeof(x)) * CHAR_BIT))) == (x))
&
(
-1ULL >>
( (sizeof(1ULL) - sizeof(x)) * CHAR_BIT )
)

== (x)
)

Now you take the byte difference between unsigned long long and the
expression, and multiply it by CHAR_BIT to get the bit difference. You
shift the max value of unsigned long long to the right by this bit
difference.

Correct: this produces the value UCHAR_MAX, UINT_MAX, ULONG_MAX or
ULLONG_MAX depending on the type of x.
You then compare this value to X, yielding an int value of
either 1 or 0, and you then bitwiseAND this with the previous value
above. I haven't fully thought it through but it seems you might have
an error above as regards operands and operator precedence.

With the missing parenthesis, the value produced in the step above is used
to mask off the high bits of val sign extended to (at least) 64 bits.
Anyway,
two flaws that stick out are:

1) Unsigned types can have padding, so sizeof isn't reliable for the
amount of value representational bits.

Good point: the C standard "supports" braindead (padding) and obsolete (non
2's complement) architectures, but the commitee forgot simple macros telling
the gory details, it is non trivial to derive INT_BIT from INT_MAX in a
macro, but can be done.
2) Use of unsigned long long is specific to C99. If we're gonna bother
with C99, then we'd be better off to go with int_max_t or whatever
it's called. As for C89, we can only go with unsigned long... but the
implemenation could provide a bigger type.

obviously, you can tailor the macro for strict c89 and c99:

#define MWELLS_CMP89(val, x) \
(((unsigned long)(val) & \
(-1UL >> ((sizeof(1UL) - sizeof(x)) * CHAR_BIT))) == (x))

#define MWELLS_CMP99(val, x) \
(((uintmax_t)(val) & \
((uintmax_t)-1 >> ((sizeof(uintmax_t) - sizeof(x)) * CHAR_BIT))) ==
(x))

Enjoy!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,483
Members
44,902
Latest member
Elena68X5

Latest Threads

Top