I was reading a book which has an exercise as:
/* howManyBits - return the minimum number of bits required to represent x in
* two's complement
* Examples: howManyBits(12) = 5
* howManyBits(0) = 1
* howManyBits(-1) = 1
* howManyBits(0x80000000) = 32
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 90
* Rating: 4
*/
int howManyBits(int x) {
return 0;
}
I don't even understand the question itself, how come 12 needs 5 bits, isn't that 1100, which is 4 bits? And how come -1 only need 1 bit? isn't that 1...1 is -1 in two's complement, so 32 bits are required?
How come 12 needs 5 bits, isn't that 1100, which is 4 bits?
With two's complement, 1 bit more is required to classify the signedness of the value. This is (usually) the left-most bit of the bit pattern, also called "most significant bit" (MSB). If this signed bit is 1 the value is negative, if it is 0 the value is positive. So you need 5 bits to represent the value 12 = 01100, not 4.
And how come -1 only needs 1 bit?
When you only have 1 bit, this bit is used for the signedness of the value too and can either represent the values 0 or -1; -1 instead of 1 since the signed bit set to 1 means negative value.
Both the 12 and the -1 points can be resolved by remembering that this is 2's complement. The question asks for the minimum number of bits needed.
In a 2's complement value, the value is negative if the highest-order bit is a 1.
A 1-bit 2's complement number can represent 2 values, -1 and 0, with the bit patterns 1b and 0b respectively.
It's a little more clear if you look at 2-bit values, which can represent:
-2: 10b
-1: 11b
0: 00b
1: 01b
Note that the 1-bit 2's complement value 1b does not represent 1. Similarly, 12 requires 5 bits: 01100b. The value of the 4-bit 1100b is -4.
A bit is used to represent the sign, 0 for positive and 1 for negative. For example, the minimum number of bits necessary to represent +12 and -12 are five:
+12 = 0 1100
-12 = 1 0100
Related
I'm trying to return the 10 least significant bits (while setting the 6 most significant bits to 0) and 6 most significant bits (while setting the 10 least significant bits to 0) from a 16-bit unsigned short and I'm stuck on how to accomplish this. For example, if I have an unsigned short 0x651A, then the bit representation would be:
// MSB LSB
// +-----------+-------------------+
// |0 1 1 0 0 1|0 1 0 0 0 1 1 0 1 0|
// +-----------+-------------------+
// | | |
// bit offset: 16 10 0
So, if I were to get the 6 most significant bits, then the returned short would be 0b0000000000011001. I'm very new to C and I'm still trying to understand bit management and bit shifting. Any advice or feedback is appreciated in helping me understand C better.
To get the least significant bits, you would use a bit mask. You apply a bitwise and to the number at hand with a mask of 10 1's in this case. The number needed can be obtained by a bitshift of 1, and then subtracting 1. That is (1 << 10)-1 in this case. So the result for any x is x & ((1 << 10)-1).
To get the most significant bits is easier. You just shift off the lower bits, eg x >> 10
So i am working on this method, but am restricted to using ONLY these operators:
<<, >>, !, ~, &, ^, |, +
I need to find if a given int parameter can be represented using 2's complement arithmetic in a given amount of bits.
Here is what I have so far:
int validRange(int val, int bits){
int minInRange = ~(1<<(bits + ~0 ))+1; //the smallest 2's comp value possible with this many bits
int maxInRange = (1<<(bits+~0))+~0; //largest 2's comp value possible
..........
}
This is what I have so far, and all I need to do now is figure out how to tell if minInRange <= val <=maxInRange. I wish I could use the greater than or less than operator, but we are not allowed. What is the bitwise way to check this?
Thanks for any help!
Two's complement negative numbers always have a '1' in their high bit.
You can convert from negative to positive (and vice versa) by converting from FF -> 00 -> 01. That is, invert the bits, add 1. (01 -> FE -> FF also works: invert the bits, add 1)
A positive number can be represented if the highest set bit in the number is within your range. (nbits - 1: 7 bits for an 8 bit signed char, etc.)
I'm not sure if your constraints allow you to use arrays. They would speed up some things but can be replaced with loops or if statements.
Anyway, if 1 << (NUM_INT_BITS-1) is set on your input, then it's negative.
Invert, add one.
Now, consider 0. Zero is a constant, and it's always the same no matter how many bits. But if you invert 0, you get "all the bits" which changes by architecture. So, ALL_BITS = ~0.
If you want to know if a positive number can be represented in 2 bits, check to see if any bits greater than or equal to bit 2 are set. Example:
two_bits = 0b00000011
any_other_bits = ~two_bits # Result: 0b11...11100
if positive_number & any_other_bits
this number is too fat for these bits!
But how do you know what ~two_bits should be? Well, it's "all set bits except the bottom however-many". And you can construct that by starting with "all set bits" and shifting them upwards (aka, "left") however-many places:
any_other_bits = ~0 << 2 # where "2" is the number of bits to check
All together now:
if (val & ((unsigned)INT_MAX + 1))
val = ~val + 1;
mask = ~0 << bits;
too_wide = val & mask;
return !too_wide;
To test if a number can be represented in a N-bit 2s compliment number: Simply test that either
The number bitwise-and'ed with the compliment of a word with the low (N-1) bits set is equal to zero
OR The high InputBitWidth-(N-1) bits of the number are 1s.
mask=(1<<(bits-1))-1; return ( !(val&mask) | !((val&~mask)^~mask) );
How do you get number like -10 from these bit shifting practice problems?
From what I understand X*32 can be written as x<<5. But how are you to get numbers like x*66, or X*(-10)?
General Explanation
Bit shifting is primarily aimed to shift the binary representation of a number. It is not for multiplication.
23 = 0001 0111
23 << 1 = 0001 0111 << 1 = 0010 1110 = 46
However, as the binary representation of a number is changed, the
number it represents is also changed. This is just how computer binary system works. And thus people sometimes exploit this behavior as a "hack", mostly to speed up the computation time.
Let's try to understand it more:
Left bit-shift and right bit-shift
Now, when the number represented is of integer type, then shifting the binary representation of a number by 1 to the left will be equivalent to multiplying it by 2:
23 = 0001 0111
23 << 1 = 0001 0111 << 1 = 0010 1110 = 46 //left bit-shift by 1, number becomes doubled
Given that there is no overflow for the given data type:
255 = 1111 1111 //assuming 8-bit data type
255 << 1 = 1111 1111 << 1 = 1111 1110 = 254 //not multiplied by 2, because of overflow
While shifting integer number to the right will be equivalent as dividing it by 2 and then rounding it down:
23 = 0001 0111
23 >> 1 = 0001 0111 >> 1 = 000 1011 = 11 //right bit-shift by 1, number becomes halved, rounded down
Some use and link to multiplication and division
Since bit-shifting operation is typically less costly than multiplication, to speed things up, you will see in some program, people use left bit-shift operation (as a replacement of multiplication) when they mean to multiply it by an integer number of power of 2 (that is 2, 4, 8, 16, etc):
int a = 23;
...
a = a << 2; //=102; multiply by 4, equivalent to a = a * 4, but faster operation
Or use right bit-shift operation (as a replacement of division and rounding down) to divide it with an integer number of power of 2 (that is 2, 4, 8, 16, etc)
int a = 23;
...
a = a >> 2; //=5; divide by 4 and rounding down, equivalent to integer division a = a / 4, but faster
Concluding remarks
Note that only if you operate with number with power of 2, all the multiplications and divisions above can be replaced by left bit-shift or right bit-shift.
In your example, 66 and -10 are not integer number which of power of 2, thus you cannot "hack" the multiplication/division with binary-shifting operation.
In general, use bit-shift operation if you mean for bit-shifting, as bit-shifting has many other uses than just "hacking" for multiplication/division with integer number of power of 2. If you want to multiply or divide, be happy with just using multiplication (*) or division (/) operator.
Some additional remarks:
That being said, I would just like to add some more things regarding the bit-shift for further explanation (it won't do harm):
signed integer type can hold positive or negative number
there is a difference between logical bit-shift and arithmetic bit-shift when dealing with negative number. One will give 0 in the emptied-space after shift while the other will give 1
Hence, it probably best to note that the bit-shift is mainly used for unsigned type, such like for creating bit-masks by bit shifting. That is, unsigned is recommended to be used to avoid sign-extension surprises when you deal with negative number (right) bit-shift.
I am reading The C Programming Language by Brian Kernigan and Dennis Ritchie. Here is what it says about the bitwise AND operator:
The bitwise AND operator & is often used to mask off some set of bits, for example,
n = n & 0177
sets to zero all but the low order 7 bits of n.
I don't quite see how it is masking the lower seven order bits of n. Please can somebody clarify?
The number 0177 is an octal number representing the binary pattern below:
0000000001111111
When you AND it using the bitwise operation &, the result keeps the bits of the original only in the bits that are set to 1 in the "mask"; all other bits become zero. This is because "AND" follows this rule:
X & 0 -> 0 for any value of X
X & 1 -> X for any value of X
For example, if you AND 0177 and 0545454, you get
0000000001111111 -- 0000177
0101010101010101 -- 0545454
---------------- -------
0000000001010101 -- 0000154
In C an integer literal prefixed with 0 is an octal number so 0177 is an octal number.
Each octal digit (of value 0 to 7) is represented with 3 bits and 7 is the greatest value for each digit. So a value of 7 in octal means 3 bits set.
Since 0177 is an octal literal and each octal number is 3 three bits you have, the following binary equivalents:
7 = 111
1 = 001
Which means 0177 is 001111111 in binary.
It is already explained that the first '0' used for octal representation of a number in ANSI C. Actually, the number 0177 (octal) is same with 127 (in decimal), which is 128-1 and also can be represented as 2^7-1, and 2^n-1 in binary representation means take n 1's and put all the 1's to the right.
0177 = 127 = 128-1
which is a bitmask;
0000000000000000000000001111111
You can check the code down below;
Demo
#include <stdio.h>
int main()
{
int n = 0177; // octal representation of 127
printf("Decimal:[%d] : Octal:[%o]\n", n, n, n);
n = 127; // decimal representation of 127
printf("Decimal:[%d] : Octal:[%o]\n", n, n, n);
return 0;
}
Output
Decimal:[127] : Octal:[177]
Decimal:[127] : Octal:[177]
0177 is an octal value each digit is represented by 3 bits form the value 000 to 111 so 0177 translates to 001111111 (i.e 001|111|111) which if you consider in 32 bit binary ( can be 64 bit too except the remainder of the digits are populated as per the MSB i.e sign bit in this case value 0) form is 0000000000000000000000001111111 and and performing a bitwise with it for a given number, will output the lower 7 bits of the number turning of rest of the digits in the n-bit number to 0.
(since x&0 =0 & x&1=x e.g 0&0=0 ,1&0=0, 1&1=1 0&1=1)
Due to the way conversions and operations are defined in C, it seems to rarely matter whether you use a signed or an unsigned variable:
uint8_t u; int8_t i;
u = -3; i = -3;
u *= 2; i *= 2;
u += 15; i += 15;
u >>= 2; i >>= 2;
printf("%u",u); // -> 2
printf("%u",i); // -> 2
So, is there a set of rules to tell under which conditions the signedness of a variable really makes a difference?
It matters in these contexts:
division and modulo: -2/2 = 1, -2u/2 = UINT_MAX/2-1, -3%4 = -3, -3u%4 = 1
shifts. For negative signed values, the result of >> and << are implementation defined or undefined, resp. For unsigned values, they are always defined.
relationals -2 < 0, -2u > 0
overflows. x+1 > x may be assumed by the compiler to be always true iff x has signed type.
Yes. Signedness will affect the result of Greater Than and Less Than operators in C. Consider the following code:
unsigned int a = -5;
unsigned int b = 7;
if (a < b)
printf("Less");
else
printf("More");
In this example, "More" is incorrectly output, because the -5 is converted to a very high positive number by the compiler.
This will also affect your arithmetic with different sized variables. Again, consider this example:
unsigned char a = -5;
signed short b = 12;
printf("%d", a+b);
The returned result is 263, not the expected 7. This is because -5 is actually treated as 251 by the compiler. Overflow makes your operations work correctly for same-sized variables, but when expanding, the compiler does not expand the sign bit for unsigned variables, so it treats them as their original positive representation in the larger sized space. Study how two's compliment works and you'll see where this result comes from.
It affects the range of values that you can store in the variable.
It is relevant mainly in comparison.
printf("%d", (u-3) < 0); // -> 0
printf("%d", (i-3) < 0); // -> 1
Overflow on unsigned integers just wraps around. On signed values this is undefined behavior, everything can happen.
The signedness of 2's complement numbers is simply just a matter of how you are interpreting the number. Imagine the 3 bit numbers:
000
001
010
011
100
101
110
111
If you think of 000 as zero and the numbers as they are natural to humans, you would interpret them like this:
000: 0
001: 1
010: 2
011: 3
100: 4
101: 5
110: 6
111: 7
This is called "unsigned integer". You see everything as a number bigger than/equal to zero.
Now, what if you want to have some numbers as negative? Well, 2's complement comes to rescue. 2's complement is known to most people as just a formula, but in truth it's just congruency modulo 2^n where n is the number of bits in your number.
Let me give you a few examples of congruency:
2 = 5 = 8 = -1 = -4 module 3
-2 = 6 = 14 module 8
Now, just for convenience, let's say you decide to have the left most bit of a number as its sign. So you want to have:
000: 0
001: positive
010: positive
011: positive
100: negative
101: negative
110: negative
111: negative
Viewing your numbers congruent modulo 2^3 (= 8), you know that:
4 = -4
5 = -3
6 = -2
7 = -1
Therefore, you view your numbers as:
000: 0
001: 1
010: 2
011: 3
100: -4
101: -3
110: -2
111: -1
As you can see, the actual bits for -3 and 5 (for example) are the same (if the number has 3 bits). Therefore, writing x = -3 or x = 5 gives you the same result.
Interpreting numbers congruent modulo 2^n has other benefits. If you sum 2 numbers, one negative and one positive, it could happen on paper that you have a carry that would be thrown away, yet the result is still correct. Why? That carry was a 2^n which is congruent to 0 modulo 2^n! Isn't that convenient?
Overflow is also another case of congruency. In our example, if you sum two unsigned numbers 5 and 6, you get 3, which is actually 11.
So, why do you use signed and unsigned? For the CPU there is actually very little difference. For you however:
If the number has n bits, the unsigned represents numbers from 0 to 2^n-1
If the number has n bits, the signed represents numbers from -2^(n-1) to 2^(n-1)-1
So, for example if you assign -1 to a an unsigned number, it's the same as assigning 2^n-1 to it.
As per your example, that's exactly what you are doing. you are assigning -3 to a uint8_t, which is illegal, but as far as the CPU is concerned you are assigning 253 to it. Then all the rest of the operations are the same for both types and you end up getting the same result.
There is however a point that your example misses. operator >> on signed number extends the sign when shifting. Since the result of both of your operations is 9 before shifting you don't notice this. If you didn't have the +15, you would have -6 in i and 250 in u which then >> 2 would result in -2 in i (if printed with %u, 254) and 62 in u. (See Peter Cordes' comment below for a few technicalities)
To understand this better, take this example:
(signed)101011 (-21) >> 3 ----> 111101 (-3)
(unsigned)101011 ( 43) >> 3 ----> 000101 ( 5)
If you notice, floor(-21/8) is actually -3 and floor(43/8) is 5. However, -3 and 5 are not equal (and are not congruent modulo 64 (64 because there are 6 bits))