0

Let's assume I have a type name called T and its size always is >= T2 both are unsigned. How do I check if a variable a of type T will fit into T2 without overflow and if it didn't fit do some other operation? I've tried check if it's became negative but I'm not sure if it's right way to check, like this:

T a = ...;
T2 b = a;
if(b < 0) // didn't fit
else // ok, fit
The Mask
  • 17,007
  • 37
  • 111
  • 185

4 Answers4

1

This won't work: if T and T2 are unsigned, b < 0 will always be false.

Since both of these types are unsigned, it is guaranteed that overflow causes wrap around (side note: if these were signed types, overflowing would cause UB, although it usually wraps around too). Thus, you can use something like:

T a = ...;
T2 b = a;
if ((T) b != a) {
   /* didn't fit */
}
else { ... }

The cast around b is not strictly necessary: if sizeof(T) > sizeof(T2), then the usual arithmetic conversions cause b to be converted to T before comparing to a. However, to make it clear, I chose to explicitly leave it there.

According to section 6.3.1.3 on C99 (see comments below), this is what happens when an unsigned integer is converted to a narrower unsigned type:

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

This means that in case of overflow, b will not be equal to a when converted back to T. That's the rationale behind this code. Moreover, it will be less than a, so you can switch != to < if you prefer.

Alternatively, you can check before assigning to b to make sure it will fit:

T a = ...;
T2 b;

if (a > (T2)-1) {
    /* won't fit */
}
else {
    /* fits */
}

(T2) -1 is the maximum value that the unsigned type T2 can hold. If a is greater than this, then it won't obviously fit into b. To see why (T2) -1 is portable and always work, see this question: Is it safe to use -1 to set all bits to true?

Community
  • 1
  • 1
Filipe Gonçalves
  • 20,783
  • 6
  • 53
  • 70
  • I know that in practice `(T2)~0ULL` is the maximum value the unsigned type `T2` can hold, but... is this guaranteed by the standard? Just playing the devil's advocate – Walter Tross Mar 03 '14 at 22:35
  • Why do you say that the cast around `b` is necessary? AFAIK, it is not, per **6.3.1.8 Usual arithmetic conversions** – Walter Tross Mar 03 '14 at 22:39
  • I'll try to answer my first question: according to **6.3.1.3 Signed and unsigned integers**, _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_, which does **not** guarantee that `(T2)~0ULL` is the maximum value the unsigned type `T2` can hold – Walter Tross Mar 03 '14 at 23:15
  • About your 2nd question: the cast is in fact unnecessary, since the usual arithmetic conversions will convert `b` to `T` if in fact `sizeof(T) > sizeof(T2)`. – Filipe Gonçalves Mar 04 '14 at 10:23
  • @WalterTross About your 1st question: you're right. I didn't even think about this rule, I basically assumed that the cast would narrow `~0ULL` down to the size of `T`. I updated my answer. – Filipe Gonçalves Mar 04 '14 at 10:37
0
T  a = ...;
T2 b = a;
if (b < a) // didn't fit
else // ok, fit

Just one byte away from what you wrote :-)

This works because for unsigned types an overflow always generates a smaller value, and the comparison will only convert b to T, which is unproblematic.

From the C99 standard (6.3.1.3 Signed and unsigned integers):

[...] 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.

This also means that the solution by Joseph Quinsey is correct: you can always compare a to (T2)-1, which is guaranteed to be the maximum value the unsigned type T2 can hold:

if (a > (T2)-1)) // won't fit

Please note that the standards never speaks of "sizes" (except when talking about sizeof), in order to remain general. Of course, the iterative procedure I cited boils down to truncating high order bits probably in all contemporary architectures, but you never know...

Walter Tross
  • 12,237
  • 2
  • 40
  • 64
  • *It is guaranteed to work only if T and T2 are both signed or unsigned* This is not true. Overflowing on signed types causes UB as per the C standard rules. – Filipe Gonçalves Mar 03 '14 at 22:05
  • sorry @FilipeGonçalves, I have completely rewitten my answer before your comment - anyways, you are right – Walter Tross Mar 03 '14 at 22:24
0

Because T2 is unsigned, your if (b < 0) will not work. The following should be ok:

    T a = ...;
    T2 b;
    if (a > (T2)-1) // a won't fit into a T2
       //...
    else
       b = a; // safe, no overflow, value unchanged

You need -1 rather than ~0, because the latter is an int, and won't work if T2 is wider than an int. (But ~0LL will work for all the usual types.)

I believe that for an unsigned type T2, its maximum value is always (T2)-1. From the C99 standard:

6.3.1.3 Signed and unsigned integers

1 When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.

2 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.49)

3 Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.

So, for example, if the maximum value of T2 is 10, then (T2)-1 is evaluated by adding 11 to -1, to get 10, as desired.

See also Question about C behaviour for unsigned integer underflow and Is it safe to assign -1 to an unsigned int to get the max value? and Signed to unsigned conversion in C - is it always safe?.

Community
  • 1
  • 1
Joseph Quinsey
  • 9,553
  • 10
  • 54
  • 77
-1

I've placed some pseudocode here to help. You should be able to get the answer from that pseudocode by tidying it up.

What you want to do is check all of the bits that are > the number of bits in T2. If any of them are "1", you can't store that particular T variable safely in a T2.

// If T is an int (32 bits) and T2 is a char (8 bits):
// assume variable a is the integer in question.

int mask = 0;
for (int i = sizeof(T2); i < sizeof(T); ++i)
{
    int mask = 1 << i;
    if (a & mask)
    {
        // if this hits, we have a "1" in that bit, so the value cannot fit.
        return false;
    }
    return true;
}
russellm
  • 155
  • 7