Home CPSC 305

Bytes and Bits

 

The Bitwise Operators

Numbers stored on a computer are stored in binary, but are usually input and output in decimal format. The mathematical operations such as addition, subtraction, multiplication and division are independent of what base a number is stored in. Bitwise operators, however, only make sense when thinking of number in binary.

The simplest bitwise operators are & and | which work like the logical && and || operators, except they work bit by bit instead of treating their arguments as one value.

For instance 5 & 6 would be calculated as:

  101
& 110
  ---
  100

And be equal to 4.

5 | 6 would be calculated as:

  101
| 110
  ---
  111

And be equal to 7.

Another bitwise operator is the ^ operator which means "exclusive or" or "xor". This operator compares bits and results in a 1 when exactly one of the two bits is set:

   101
 ^ 110
   ---
   011

So 5 ^ 6 = 3.

These three operators are summarized in the following truth table:

ABA & BA | BA ^ B
00000
01011
10011
11110

 

Negations

The ! operator is used for logical negation. The bitwise operator for negation is ~. The result of a bitwise negation very much depends on the size of the values being negated. For example ~5 as a single byte:

~ 00000101
----------
  11111010 = 250

However, if we are using 4 byte integers:

~ 00000000000000000000000000000101
----------------------------------
  11111111111111111111111111111010 = 4294967290


 

Shifts

Another thing we can do with bits is shift them left or right. For example, if we shift 5 left three spots:

   101
<<   3
------
101000 = 40

Shifting left by 3 is the same thing as multiplying by 8. In general, shifting left by $N$ is the same as multiplying by $2^N$.

We can also shift right using the >> operator:


10010 = 18
>>  2
-----
  100 = 4

When shifting right, the bits on the right are lost. So the rightmost 10 are shifted off of the number.

Notice that shifting right by 2 is the same as dividing by 4 (and dropping any remainder. In general, shifting right by $N$ is the same as dividing by $2^N$.


 

Example Program

This program tests all of the bitwise operators. Notice it uses unsigned chars as the data type which can store numbers from 0 - 255. The %hhu format specifier is for unsigned chars:


#include <stdio.h>

int main() {
    unsigned char a, b;

    printf("Enter two numbers [0 - 255]: ");
    scanf("%hhu %hhu", &a, &b);

    printf("%hhu & %hhu = %hhu\n", a, b, a & b);
    printf("%hhu | %hhu = %hhu\n", a, b, a | b);
    printf("%hhu ^ %hhu = %hhu\n", a, b, a ^ b);
    printf("%hhu << %hhu = %hhu\n", a, b, a << b);
    printf("%hhu >> %hhu = %hhu\n", a, b, a >> b);
    printf("~%hhu = %hhu\n", a, ~a);
    printf("~%hhu = %hhu\n", b, ~b);

    return 0;
}

 

Testing a Bit

One thing we will want to do with the bitwise operators is treat a large value as a several individual values packed together. For instance, we might view a 32-bit integer as eight 4-bit values, 32 one-bit values, or any other combination.

You can only directly read or write an entire byte, but with the bitwise operators, we can read and write bits.

How could we write a function that tells us if a single bit in a value is set or not?

With such a function, we could write a program to print a number in binary:


/* returns 1 if the bit in value is set, and 0 otherwise */
int test_bit(unsigned int value, int bit_number) {
    int mask = 1 << bit_number;

    if ((value & mask) == 0) {
        return 0;
    } else {
        return 1;
    }
}

 

Setting a Bit

Likewise, can we write a function to set a given bit in a number, and make it 1?


/* sets a bit in a number to 1 */
void set_bit(unsigned int* value, int bit_number) {
    int mask = 1 << bit_number;

    *value = *value | mask;
}

 

Clearing a Bit

How could we clear a bit which would set it to zero?


/* clear a bit in a number to 0 */
void clear_bit(unsigned int* value, int bit_number) {
    int mask = ~(1 << bit_number);

    *value = *value & mask;
}

 

Flipping a Bit

Here we want to flip, or invert a single bit in a number. How can we do that?


/* flip a given bit in a number */
void flip_bit(unsigned int* value, int bit_number) {
    int mask = 1 << bit_number;

    *value = *value ^ mask;
}

These functions can be found in bits.c.


 

File I/O in C

We have seen the printf and scanf functions for doing I/O in C. There are versions of these functions call fprintf and fscanf which work similarly except that they work with files instead of standard input and output.

To use them, we must create a FILE* object, which is a pointer to a FILE structure.

To create a file, we use the fopen function which takes the name of the file as the first parameter. The second parameter is the mode string which is one of the following:

ModeMeaning
rOpen for reading.
r+Open for reading and writing, file is not overwritten.
wOpen for writing.
w+Open for reading and writing, but file is overwritten if exists.
aOpen for appending (writes at end of file).
a+Open for reading and appending.

To read a number from an input file, we could use:


#include <stdio.h>

int main() {
    FILE* file = fopen("data.txt", "r");

    int number;
    fscanf(file, "%d", &number);
    printf("Read %d\n", number);

    fclose(file);

    return 0;
}

 

Binary Input and Output

C also has functions for reading and writing binary data. The fread function takes the following parameters:

fread returns the number of elements which were successfully read. You should check that this is equal to the number you attempted to read.

The following example shows how we could read in an array of 5 numbers stored in binary:


int array[5];
if (fread(array, sizeof(int), 5, file) != 5) {
    /* there was an error */
}

The analogous function for output is fwrite, which takes exactly the same parameters as fread, except that it writes the data to a file instead of reading it.

An example which would write the binary values above would look like:


int array[5];
if (fwrite(array, sizeof(int), 5, file) != 5) {
    /* there was an error */
}

This programs reads 5 ints from the user and writes them to a file in binary. Notice that the file will not be human-readable afterwards! You can use the hexdump -C command to view the file.

Copyright © 2024 Ian Finlayson | Licensed under a Attribution-NonCommercial 4.0 International License.