Skip to content

VOL 03 / CH 01 / LESSON 01

1.1 Bits, Bytes, and Number Systems ​

Below the previous volume’s algorithms, data becomes bit patterns that hardware can represent and process. This lesson establishes the representation model used for both numbers and text.

Volume three moves from algorithms down to the machine layer. This lesson establishes bits, bytes, object representations, and number bases. The next section covers Unicode and UTF, followed by Byte Order and Bit Masks.

A Bit Pattern Is Not Inherently "The Number 42" ​

Write one byte as 00101010 and ask what it means. It could be the unsigned integer 42, the ASCII character *, a color component, or part of an instruction. A bit pattern carries no inherent type; the storage format and reading program define its meaning together.

Hardware encodes information using physical states (voltage, charge, magnetic orientation, or other physical properties) with thresholds dependent on manufacturing processes and interfaces. Specifying 1 as 3.3V or 5V is no longer valid for modern CPU internals. The software layer relies only on abstracted, stable binary states.

A bit has two possible values. With n bits, you can distinguish 2^n distinct patterns. When interpreted as an unsigned integer, the range is from 0 to 2^n-1.

This book assumes a byte is an 8-bit octet, a standard widely used in networking protocols and modern general-purpose platforms. In ISO C, a byte is defined as a unit of size equal to char, and the number of bits is given by CHAR_BIT; the standard guarantees at least 8 bits, though it does not require all implementations to have exactly 8.

Position Determines a Bit's Weight ​

The decimal number 4205 equals 4*10^3 + 2*10^2 + 5. Binary, octal, and hexadecimal are just different bases, using 2, 8, and 16 respectively. Binary 101010 equals 42.

A single hexadecimal digit corresponds exactly to 4 bits, making it a common choice in system documentation for compactly displaying bit patterns:

text
0x2A = 0010 1010
0xFF = 1111 1111

An octal digit represents exactly 3 bits. Unix permissions are often written as 0755 because each group of three permission bits fits neatly into a single octal digit.

A Compilable Conversion Program ​

This C17 program does not assume that unsigned int is exactly 32 bits. Save it as encoding_numbers.c. The buffer uses sizeof and CHAR_BIT, and the program reports the implementation’s bits per byte. The caller supplies a writable buffer of at least capacity bytes. A null pointer or insufficient capacity produces failure without changing the output.

c
#include <limits.h>
#include <stdio.h>

static int to_binary(unsigned int value, char *output, size_t capacity) {
    char reversed[sizeof(unsigned int) * CHAR_BIT];
    size_t length = 0;

    if (output == NULL || capacity < 2) {
        return 0;
    }
    if (value == 0) {
        output[0] = '0';
        output[1] = '\0';
        return 1;
    }

    while (value != 0) {
        reversed[length++] = (char)('0' + value % 2U);
        value /= 2U;
    }
    if (length + 1 > capacity) {
        return 0;
    }
    for (size_t index = 0; index < length; index++) {
        output[index] = reversed[length - index - 1];
    }
    output[length] = '\0';
    return 1;
}

int main(void) {
    char binary[sizeof(unsigned int) * CHAR_BIT + 1];
    if (!to_binary(42U, binary, sizeof binary)) {
        return 1;
    }

    printf("CHAR_BIT=%d\n", CHAR_BIT);
    printf("42 = %s (base 2) = %#o (base 8) = %#x (base 16)\n",
           binary, 42U, 42U);
    return 0;
}

Compile and run:

bash
cc -std=c17 -Wall -Wextra -Werror encoding_numbers.c -o encoding_numbers
./encoding_numbers

On a typical platform with 8-bit bytes, the output is:

text
CHAR_BIT=8
42 = 101010 (base 2) = 052 (base 8) = 0x2a (base 16)

C17 does not have a standard binary integer literal. Some compilers accept 0b101010 as an extension; C23 finally brought 0b and 0B into the standard. Integer literals starting with zero are interpreted in octal, and 010 represents decimal 8. This historical syntax is prone to configuration errors.

From Decimal Hand Calculation to Any Base ​

To convert a non-negative integer to base b, repeatedly divide by b, record the remainders, and then read them in reverse order. For example, converting 42 to binary:

DivisionQuotientRemainder
42 / 2210
21 / 2101
10 / 250
5 / 221
2 / 210
1 / 201

Reading from bottom to top yields 101010. Reverse conversion can be performed by repeatedly applying result = result * base + digit from left to right.

When doing manual calculations, first estimate the range. Eight bits are insufficient to represent 256; two hexadecimal digits can represent at most 0xFF = 255. Range checking helps detect misalignment before the full computation begins.

What's Stored in Memory Is the Object's Byte Representation ​

C permits inspecting a valid, readable object’s representation through unsigned char *. Save the following independent program as object_bytes.c; do not put both main functions in one file. The inspector reads representation bytes without converting the object’s value:

c
#include <stdint.h>
#include <stdio.h>

static void print_bytes(const void *object, size_t size) {
    const unsigned char *bytes = object;
    for (size_t index = 0; index < size; index++) {
        printf("%02X%s", (unsigned int)bytes[index], index + 1 == size ? "\n" : " ");
    }
}

int main(void) {
    uint32_t value = UINT32_C(0x12345678);
    print_bytes(&value, sizeof value);
    return 0;
}

The output is listed in order from low to high memory address, with the exact byte ordering explained in the endianness chapter. Never hardcode the byte order from one machine's output into a portable file format; cross-platform data must explicitly define field widths and byte order.

Object representations may contain padding bits or bytes; padding is not a reliable basis for comparing object values. uint32_t is an optional exact-width unsigned type: if provided, it has exactly 32 value bits and no padding bits.

bit, byte, and capacity units ​

NameMeaning
bit (b)A binary digit, one binary position
byte (B)8 bits in this book; in ISO C, the addressable character storage unit
octetA clearly defined 8-bit unit
KiB1024 bytes
MiB1024 KiB
kB1000 bytes (SI standard)

Network bandwidth is typically measured in bit per second (bit/s), while file sizes are expressed in bytes. A 100 Mb/s link corresponds to a raw rate of 12.5 MB/s; application payload throughput is lower after protocol overhead. Unit casing is not a formatting detail.

Common Boundaries ​

  • Number formatting is not a storage format. 42, 052, and 0x2A represent the same value in C source code.
  • The printf format specifier must match the type of the actual argument; otherwise, undefined behavior results.
  • Negative integers cannot be handled by the unsigned base conversion rules described in this section. The next chapter will begin with two's complement and signed value ranges.
  • Eight bits have 256 possible patterns, with a maximum unsigned value of 255.
  • When parsing binary protocols, do not directly cast a buffer to a struct pointer; alignment, padding, byte order, and aliasing rules all affect the outcome.

Hands-on Check of Representation ​

  1. Expand to_binary to any radix between 2 and 16, and reject invalid radices.
  2. Manually compute the decimal values of 0x7F, 0x80, and 0755.
  3. Query the compiler for the values of CHAR_BIT, sizeof(int), and UINT_MAX.
  4. Explain why the RGB color #FF8040 is well-suited for hexadecimal representation.
  5. Distinguish between a character, a Unicode code point, and a single byte in UTF-8.

The Byte Stream Starts to Represent Text ​

So far, we've only assigned numerical meanings to bit patterns. In the next section, we'll explore Character Sets, Unicode, and UTF Encoding: the same "character" can involve code points, encoding units, bytes, and the actual visual glyph seen by the user. These layers must be separated and understood distinctly.

Built with VitePress | Software Systems Atlas