1

I have written a program to find number of bits on a given machine.

#include <stdio.h>

int int_size(void);

int main(int argc, char *argv[])
{
    printf("%d\n", int_size());
    
 
    return 0;
}

int int_size(void)
{
    int count = 1;
    int value = 1;

    while (1)
    {   
        value <<= 1;
        if (value > 0)
        {
            count++;
        }
        else
        {
            break;
        }

    }

    return count + 1;
}

Now idea behind this is as follows. On most of the systems, left most bit is 1 for negative numbers. So, I start with 1. If the system used 8 bits for storing an integer, then 1 would be represented as 0000 0001. I then go on left shifting this number and then check whether its positive. Now, when number becomes, 1000 0000, left most bit is 1 and this would be taken as a negative number on most systems. So, while loop is exited and I get the count for the bits. I have tried this program on 7 online C compilers and all return value 32. So an integer has 32 bits on these systems. Now, I still don't know if my code is portable enough. Are there any machines that do not have left most bit as 1 for negative numbers ? I have come across three methods to store negative number. two's complement, one's complement and sign + magnitude. All three have left most bit 1 for negative numbers. So, this method should work on all systems.

user9026
  • 852
  • 2
  • 9
  • 20
  • 4
    Signed integer overflow causes *undefined behavior*. `CHAR_BITS * sizeof(int)` may be better. – MikeCAT Mar 17 '23 at 13:12
  • 2
    "I still don't know if my code is portable enough". No it's not. – Support Ukraine Mar 17 '23 at 13:13
  • @Mike, I am not causing integer overflow here. When the leftmost bit is 1, loop is exited. – user9026 Mar 17 '23 at 13:17
  • As @MikeCAT said, why even bother if you have sizeof which gives you this information – KubaJ Mar 17 '23 at 13:17
  • See, the compiler for a given architecture *knows* the size of the data types on this architecture, so there is no point to write a program to "determine" them in the run-time. Just ask the compiler as, for example @MikeCAT have suggested, or by examining the provided `INT_MAX/INT_MIN` values. – Eugene Sh. Mar 17 '23 at 13:18
  • 1
    @user9026: Shifting a 1 into the sign bit of an `int` is integer overflow. The C standard defines left-shifting as a multiplication by a power of 2, and the mathematical result of a one-bit shift of 2^30 would be 2^31, but a 32-bit `int` cannot represent that value, so it overflows what an `int` can represent. – Eric Postpischil Mar 17 '23 at 13:18
  • @KubaJ: `sizeof` includes padding bits, so it is not a fully portable way to determine the width of an integer type. – Eric Postpischil Mar 17 '23 at 13:18
  • @KubaJ, I know that solution. I was trying to just do some basic bit manipulation to see how solution would look like. – user9026 Mar 17 '23 at 13:19
  • You can determine the width using shifting by using `unsigned` instead of `int`. For `unsigned`, the shifting behavior is fully defined. – Eric Postpischil Mar 17 '23 at 13:19
  • Related: [Is there any way to compute the width of an integer type at compile-time?](https://stackoverflow.com/questions/3957252/is-there-any-way-to-compute-the-width-of-an-integer-type-at-compile-time) – Eric Postpischil Mar 17 '23 at 13:21

3 Answers3

3

Now, I still don't know if my code is portable enough.

Your code has undefined behavior so the answer is: No

From C (draft) standard N1570:

The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 × 2E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 × 2E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

When your value is at 230 and you do one more left shift, the result is 231 which isn't representable in an int. Therefore you end in the "otherwise, the behavior is undefined" part.

If you don't want to use sizeof then you can write a program using unsigned types and test for zero for detecting the overflow. You actually already do that so all you need is unsigned int value = 1; and you are safe.

That will work because the standard says:

For each of the signed integer types, there is a corresponding (but different) unsigned integer type (designated with the keyword unsigned) that uses the same amount of storage (including sign information) ...

Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
2

Now, I still don't know if my code is portable enough.

The program does not conform strictly to the C language specification. That's pretty much the definition of "non-portable" as far as the language spec is concerned.

I have come across three methods to store negative number. two's complement, one's complement and sign + magnitude. All three have left most bit 1 for negative numbers.

These are the three alternatives that C provides for, prior to C23. All conforming implementations will use one of these.

So, this method should work on all systems.

Not at all. Shifts are operations on integer values, not on their representations. You cannot conclude from the fact that there is a sign bit in each representation that you can ever produce a negative value by performing a left shift on a positive one. The result of left-shifting a positive value of signed integer type is undefined if the type cannot represent the arithmetic result.

Generally speaking, you should use unsigned integer types when working with shifts and bitwise operations. In particular, left-shifting a non-zero unsigned integer value until it becomes zero can give you information about the number of value bits in the type.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
1

I believe it is portable as shift operations on unsigned numbers are well defined and the assignment of signed numbers with unsigned numbers is well defined too:

int int_size(void)
{
    int count = 0;
    int value = ~0;

    while ((value = ((unsigned)value) >> 1))
    {   
        count++;
    }

    return count + 1;
}
0___________
  • 60,014
  • 4
  • 34
  • 74