> I never really understood how shifting bits works. I mean, how will I
> know -1 will return -16 after shifting 4 bits (if you replace %x with
> with %d, you get -16). Is it possible to know in advance e.g what
> shifting 8 bits to left on 100 will result.
If you understand how two's complement binary numbers are represented,
it's not that difficult. 100 << 8 equals 25600, assuming you are
calculating with variables sufficiently wide to hold the numbers
(and int is guaranteed to hold those numbers).
Note: C doesn't guarantee that your machine uses two's complement.
It could use one's complement or sign/magnitude. You should know
what those are but pretty much all of the machines you'll actually
run into use two's complement.
> What practical use is made of shifting bits ?
Shifting has a number of uses:
- It's sometimes an optimization for multiplication (left shifts)
or division (right shifts). You have to be careful with this:
some optimizations work only for non-negative numbers that don't
overflow. If you write multiplication and division (especially
by power-of-two constants), the compiler may generate shifts
instead, or do the math at compile time.
- Some data (particularly i/o registers for peripherals on embedded
systems, and some fields in network packet headers) is defined
as a bunch of separate fields stored within an integer. Packing
and unpacking this data may easily be done with shifting and
masking. (Why not use bit fields? Because there's no portable
way to ensure that a given C bit field declaration actually matches
the hardware, and if it actually does, that the next version of
the compiler will agree.)
- Serial data transmission inherently involves shifting data and
sending one bit at a time, and receiving it involves picking up
that data and packing it into characters. Often this is done by
hardware, but some embedded machines don't have the hardware and
use a "software UART" replacement.
- Some algorithms, especially in cryptography and data compression,
are defined in terms of bit shifts or packing a bunch of
variable-bit-length data elements together. This also includes
'uuencode' and the process of converting from Unicode code points
to UTF-8.
- Certain conversion algorithms, like taking a printable octal or
hex number and putting its value into an int, just involve it
naturally. You just convert the printable characters to 3-bit
or 4-bit fields and pack them together.
- You can combine a bunch of miscellaneous flags into one integer
value, and access them independently. The common octal representation
of UNIX file permissions is one example of this.
- Sometimes one is short on storage and wants to make a big bit array.
You pick some unit of storage, say, unsigned short, and assume it
has at least 16 bits. Then you can access any individual bit N in
"bigarray", an array of unsigned shorts, like this:
(bigarray[(N)/16] & (1 << ((N)%16)))
which is zero or nonzero depending on the given bit.
This can be rewritten:
(bigarray[(N)>>4] & (1 << ((N) & 0x0f)))