Computer Systems Fundamentals

Course Resources

Administrivia: Course Outline | COMP1521 Handbook
Administrivia: Course Timetable | Help Sessions
Meet the Team: Our Team
Platforms: Lecture Recordings | Online Tut-Labs and Help Sessions (via BbCollaborate) | Course Forum
Style Guides: COMP1521 C Style Guide | Assembly Style Guide
MIPS Resources: MIPS Documentation | Text Editors for Assembly
mipsy: mipsy-web | mipsy source code
Resources: Linux Cheatsheet | C Reference
Assessment: Autotests, Submissions, Marks | Give online: submission | Give online: sturec
Assignments: Assignment 1

Course Content Week-by-Week

Tutorial
Laboratory
Extra Revision
Monday Week 1
Wednesday Week 1
Tutorial
Laboratory
Monday Week 2
Wednesday Week 2
Tutorial
Laboratory
Weekly Test
Monday Week 3
Wednesday Week 3
Tutorial
Laboratory
Weekly Test
Monday Week 4
Wednesday Week 4
Tutorial
Laboratory
Monday Week 5
Wednesday Week 5

Course Content Topic-by-Topic

Mips Basics

Square two numbers and sum their squares.

#include <stdio.h>

int main(void) {
    int a, b;

    printf("Enter a number: ");
    scanf("%d", &a);

    printf("Enter another number: ");
    scanf("%d", &b);

    printf("The sum of the squares of %d and %d is %d\n", a, b, a*a + b*b);

    return 0;
}

Download square_and_add.c


Square two numbers and sum their squares.

#include <stdio.h>

int main(void) {
    int a, b;

    printf("Enter a number: ");
    scanf("%d", &a);

    printf("Enter another number: ");
    scanf("%d", &b);


    printf("The sum of the squares of ");
    printf("%d", a);
    printf(" and ");
    printf("%d", b);
    printf(" is ");

    a = a * a;
    b = b * b;
    printf("%d", a + b);
    putchar('\n');

    return 0;
}

Download square_and_add.simple.c


Square and add two numbers and print the result.

	.text
main:
	# Locals:
	# - $t0: int a
	# - $t1: int b

	li	$v0, 4			# syscall 4: print_string
	la	$a0, prompt1_msg	#
	syscall				# printf("Enter a number: ");

	li	$v0, 5			# syscall 5: read_int
	syscall				#
	move	$t0, $v0		# scanf("%d", &a);

	li	$v0, 4			# syscall 4: print_string
	la	$a0, prompt2_msg	#
	syscall				# printf("Enter another number: ");

	li	$v0, 5			# syscall 5: read_int
	syscall				#
	move	$t1, $v0		# scanf("%d", &b);


	li	$v0, 4			# syscall 4: print_string
	la	$a0, result_msg_1	#
	syscall				# printf("The sum of the squares of ");

	li	$v0, 1			# syscall 1: print_int
	move	$a0, $t0		#
	syscall				# printf("%d", a);

	li	$v0, 4			# syscall 4: print_string
	la	$a0, result_msg_2	#
	syscall				# printf(" and ");

	li	$v0, 1			# syscall 1: print_int
	move	$a0, $t1		#
	syscall				# printf("%d", b);

	li	$v0, 4			# syscall 4: print_string
	la	$a0, result_msg_3	#
	syscall				# printf(" is ");

	mul	$t0, $t0, $t0		# a = a * a;
	mul	$t1, $t1, $t1		# b = b * b;

	li	$v0, 1			# syscall 1: print_int
	add	$a0, $t0, $t1		#
	syscall				# printf("%d", a + b);

	li	$v0, 11			# syscall 11: print_char
	la	$a0, '\n'		#
	syscall				# putchar('\n');


	li	$v0, 0
	jr	$ra			# return 0;

	.data
prompt1_msg:
	.asciiz	"Enter a number: "
prompt2_msg:
	.asciiz	"Enter another number: "
result_msg_1:
	.asciiz	"The sum of the squares of "
result_msg_2:
	.asciiz	" and "
result_msg_3:
	.asciiz	" is "

Download square_and_add.s

Mips Control

Print a message only if a number is even.

#include <stdio.h>

int main(void) {
    int n;
    printf("Enter a number: ");
    scanf("%d", &n);

    if (n % 2 == 0) {
        printf("even\n");
    }

    return 0;
}

Download print_if_even.c


Print a message only if a number is even.

#include <stdio.h>

int main(void) {
    int n;
    printf("Enter a number: ");
    scanf("%d", &n);

    if (n % 2 != 0) goto epilogue;
        printf("even\n");

epilogue:
    return 0;
}

Download print_if_even.simple.c


Calculate 1*1 + 2*2 + ... + 99*99 + 100*100

#include <stdio.h>

int main(void) {
    int sum = 0;

    for (int i = 1; i <= 100; i++) {
        sum += i * i;
    }

    printf("%d\n", sum);

    return 0;
}

Download sum_100_squares.c


Calculate 1*1 + 2*2 + ... + 99*99 + 100*100.

#define UPPER_BOUND 100
#include <stdio.h>

int main(void) {
    int sum = 0;

loop_i_to_100__init:;
    int i = 0;
loop_i_to_100__cond:
    if (i > UPPER_BOUND) goto loop_i_to_100__end;
loop_i_to_100__body:
    sum += i * i;
loop_i_to_100__step:
    i++;
    goto loop_i_to_100__cond;
loop_i_to_100__end:

    printf("%d", sum);
    putchar('\n');

    return 0;
}

Download sum_100_squares.simple.c


Calculate 1*1 + 2*2 + ... + 99*99 + 100*100

UPPER_BOUND = 100


	.text
main:
	# Locals:
	# - $t0: int sum
	# - $t1: int i
	# - $t2: temporary value

	li	$t0, 0					# int sum = 0;

loop_i_to_100__init:
	li	$t1, 1					# int i = 0;
loop_i_to_100__cond:
	bgt	$t1, UPPER_BOUND, loop_i_to_100__end	# while (i < UPPER_BOUND) {
loop_i_to_100__body:
	mul	$t2, $t1, $t1				#   sum = (i * i) +
	add	$t0, $t0, $t2				#         sum;
loop_i_to_100__step:
	addi	$t0, $t0, 1				#   i++;
	b	loop_i_to_100__cond			# }

loop_i_to_100__end:
	li	$v0, 1					# syscall 1: print_int
	move	$a0, $t0				# 
	syscall						# printf("%d", sum);

	li	$v0, 11					# syscall 11: print_char
	li	$a0, '\n'				#
	syscall						# putchar('\n');

	li	$v0, 0
	jr	$ra					# return 0;

Download sum_100_squares.s

Mips Data
#include <stdio.h>

int x, y, z;

int main(void) {
    x = 17;
    y = 25;
    z = x + y;
    printf("%d", z);
    printf("\n");
    return 0;
}

Download add_memory.c


Add 17 and 25 using variables stored in memory and print result.
main:
	li	$t0, 17
	la	$t1, x
	sw	$t0, 0($t1)		# x = 17;

	li	$t0, 25
	la	$t1, y
	sw	$t0, 0($t1)		# y = 25;

	la	$t0, x
	lw	$t1, 0($t0)
	la	$t0, y
	lw	$t2, 0($t0)
	add	$t3, $t1, $t2
	la	$t0, z
	sw	$t3, 0($t0)		# z = x + y;

	li	$v0, 1			# syscall 1: print_int
	la	$t0, z			#
	lw	$a0, 0($t0)		#
	syscall				# printf("%d", z);

	li	$v0, 11			# syscall 11: print_char
	li	$a0, '\n'		#
	syscall				# putchar('\n');

	li	$v0, 0
	jr	$ra			# return 0;

	.data
x:
	.space	4
y:
	.space	4
z:
	.space	4

Download add_memory.s


Add 17 and 25 using variables stored in memory and print result.
main:
	la	$t0, x
	lw	$t1, 0($t0)
	la	$t0, y
	lw	$t2, 0($t0)

	add	$t3, $t1, $t2
	la	$t0, z
	sw	$t3, 0($t0)		# z = x + y;

	li	$v0, 1			# syscall 1: print_int
	la	$t0, z			#
	lw	$a0, 0($t0)		#
	syscall				# printf("%d", z);

	li	$v0, 11			# syscall 11: print_char
	li	$a0, '\n'		#
	syscall				# putchar('\n');

	li	$v0, 0
	jr	$ra			# return 0;

	.data
x:
	.word	17
y:
	.word	25
z:
	.space	4

Download add_memory_initialized.s

#include <stdio.h>

int x[] = {17, 25, 0};
int main(void) {
    x[2] = x[0] + x[1];
    printf("%d", x[2]);
    printf("\n");
    return 0;
}

Download add_memory_array.c


Add 17 and 25 using variables stored in an array, and print the result.
main:
	la	$t0, x
	lw	$t1, 0($t0)
	lw	$t2, 4($t0)
	add	$t3, $t1, $t2		# x[2] = x[0] + x[1];
	sw	$t3, 8($t0)

	li	$v0, 1			# syscall 1: print_int
	lw	$a0, 8($t0)		#
	syscall				# printf("%d", x[2]);

	li	$v0, 11			# syscall 11: print_char
	li	$a0, '\n'		#
	syscall				# putchar('\n');

	li	$v0, 0
	jr	$ra			# return 0;

	.data

x:	.word 17, 25, 0			# int x[] = {17, 25, 0}

Download add_memory_array.s


Simple example of accessing an array element
#include <stdio.h>

int array[10];

int main(void) {
    array[3] = 17;
}

Download store_array_element.c

main:
	li	$t0, 3			# (3 *
	mul	$t0, $t0, 4		#  sizeof(int)
	la	$t1, x			#
	add	$t2, $t1, $t0		#  + x) = &x[3]

	li	$t3, 17
	sw	$t3, 0($t2)		# x[3] = 17;

	.data
x:	.space	4*10				# int x[10];

Download store_array_element.s

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


int main(void) {
    double array[10];

    for (int i = 0; i < 10; i++) {
        printf("&array[%d]=%p\n", i, &array[i]);
    }

    printf("\nExample computation for address of array element\n");

    uintptr_t a = (uintptr_t)&array[0];
    printf("&array[0] + 7 * sizeof (double) = 0x%lx\n",     a + 7 * sizeof (double));
    printf("&array[0] + 7 * %lx               = 0x%lx\n", sizeof (double), a + 7 * sizeof (double));
    printf("0x%lx + 7 * %lx          = 0x%lx\n", a, sizeof (double), a + 7 * sizeof (double));
    printf("&array[7]                       = %p\n", &array[7]);
}

Download array_element_address.c

Mips Functions

C Function with No Parameters or Return Value
#include <stdio.h>

void f(void);

int main(void) {
    printf("calling function f\n");
    f();
    printf("back from function f\n");
    return 0;
}

void f(void) {
    printf("in function f\n");
}

Download call_return.c

simple example of returning from a function loops because main does not save return address
main:
	la	$a0, string0		# printf("calling function f\n");
	li	$v0, 4
	syscall

	jal	f			# set $ra to following address

	la	$a0, string1		# printf("back from function f\n");
	li	$v0, 4
	syscall

	li	$v0, 0			# return from function main
	jr	$ra			# breaks because $ra has been changed since main called


f:
	la	$a0, string2		# printf("in function f\n");
	li	$v0, 4
	syscall
	jr	$ra			# return from function f


	.data
string0:
	.asciiz "calling function f\n"
string1:
	.asciiz "back from function f\n"
string2:
	.asciiz "in function f\n"

Download call_return.broken.s

simple example of placing return address on stack note stack grows down
main:
	addi	$sp, $sp, -4		# move stack pointer down to make room
	sw	$ra, 0($sp)		# save $ra on $stack

	la	$a0, string0		# printf("calling function f\n");
	li	$v0, 4
	syscall

	jal	f			# set $ra to following address

	la	$a0, string1		# printf("back from function f\n");
	li	$v0, 4
	syscall

	lw	$ra, 0($sp)		# recover $ra from $stack
	addi	$sp, $sp, 4		# move stack pointer back to what it was

	li	$v0, 0			# return 0 from function main
	jr	$ra			#


f:
	la	$a0, string2		# printf("in function f\n");
	li	$v0, 4
	syscall
	jr	$ra			# return from function f


	.data
string0:
	.asciiz "calling function f\n"
string1:
	.asciiz "back from function f\n"
string2:
	.asciiz "in function f\n"

Download call_return_raw.s

simple example of placing return address on stack begin, end, push pop are pseudo-instructions provided by mipsy but not spim
main:
	push	$ra			# save $ra on $stack

	la	$a0, string0		# printf("calling function f\n");
	li	$v0, 4
	syscall

	jal	f			# set $ra to following address

	la	$a0, string1		# printf("back from function f\n");
	li	$v0, 4
	syscall

	pop	$ra			# recover $ra from $stack

	li	$v0, 0			# return 0 from function main
	jr	$ra			#


# f is a leaf function so it doesn't need an epilogue or prologue
f:
	la	$a0, string2		# printf("in function f\n");
	li	$v0, 4
	syscall
	jr	$ra			# return from function f


	.data
string0:
	.asciiz "calling function f\n"
string1:
	.asciiz "back from function f\n"
string2:
	.asciiz "in function f\n"

Download call_return.s

simple example of returning a value from a function
#include <stdio.h>

int answer(void);

int main(void) {
    int a = answer();
    printf("%d\n", a);
    return 0;
}

int answer(void) {
    return 42;
}

Download return_answer.c

simple example of returning a value from a function note storing of return address $ra
code for function main
main:
	begin				# move frame pointer
	push	$ra			# save $ra onto stack

	jal	answer			# call answer(), return value will be in $v0

	move	$a0, $v0		# printf("%d", a);
	li	$v0, 1			#
	syscall				#

	li	$a0, '\n'		# printf("%c", '\n');
	li	$v0, 11			#
	syscall				#

	pop	$ra			# recover $ra from stack
	end				# move frame pointer back

	li	$v0, 0			# return
	jr	$ra			#


# code for function answer
answer:
	li	$v0, 42			# return 42
	jr	$ra			#

Download return_answer.s

example of function calls
#include <stdio.h>

int sum_product(int a, int b);
int product(int x, int y);

int main(void) {
    int z = sum_product(10, 12);
    printf("%d\n", z);
    return 0;
}

int sum_product(int a, int b) {
    int p = product(6, 7);
    return p + a + b;
}

int product(int x, int y) {
    return x * y;
}

Download more_calls.c

example of function calls note storing of return address $a0, $a1 and $ra on stack
main:
	begin				# move frame pointer
	push	$ra			# save $ra onto stack

	li	$a0, 10			# sum_product(10, 12);
	li	$a1, 12
	jal	sum_product

	move	$a0, $v0		# printf("%d", z);
	li	$v0, 1
	syscall

	li	$a0, '\n'		# printf("%c", '\n');
	li	$v0, 11
	syscall

	pop	$ra			# recover $ra from stack
	end				# move frame pointer back

	li	$v0, 0			# return 0 from function main
	jr	$ra			# return from function main



sum_product:
	begin				# move frame pointer
	push	$ra			# save $ra onto stack
	push	$s0			# save $s0 onto stack
	push	$s1			# save $s1 onto stack

	move	$s0, $a0		# preserve $a0 for use after function call
	move	$s1, $a1		# preserve $a1 for use after function call

	li	$a0, 6			# product(6, 7);
	li	$a1, 7
	jal	product



	add	$v0, $v0, $s0		# add a and b to value returned in $v0
	add	$v0, $v0, $s1		# and put result in $v0 to be returned

	pop	$s1			# recover $s1 from stack
	pop	$s0			# recover $s0 from stack
	pop	$ra			# recover $ra from stack
	end				# move frame pointer back

	jr	$ra			# return from sum_product


product:				# product doesn't call other functions
                        # so it doesn't need to save any registers
	mul	$v0, $a0, $a1		# return argument * argument 2
	jr	$ra			#

Download more_calls.s

recursive function which prints first 20 powers of two in reverse
#include <stdio.h>

void two(int i);

int main(void) {
    two(1);
}

void two(int i) {
    if (i < 1000000) {
        two(2 * i);
    }
    printf("%d\n", i);
}

Download two_powerful.c

simple example of placing return address ($ra) and $a0 on the stack recursive function which prints first 20 powers of two in reverse
main:
	begin				# move frame pointer
	push	$ra			# save $ra onto stack

	li	$a0, 1
	jal	two			# two(1);

	pop	$ra			# recover $ra from stack
	end				# move frame pointer back

	li	$v0, 0			# return 0
	jr	$ra			#


two:
	begin				# move frame pointer
	push	$ra			# save $ra onto stack
	push	$s0			# save $s0 onto stack

	move	$s0, $a0

	bge	$a0, 1000000, two_end_if
	mul	$a0, $a0, 2
	jal	two
two_end_if:

	move	$a0, $s0
	li	$v0, 1			# printf("%d");
	syscall

	li	$a0, '\n'		# printf("%c", '\n');
	li	$v0, 11
	syscall

	pop	$s0			# recover $s0 from stack
	pop	$ra			# recover $ra from stack
	end				# move frame pointer back

	jr	$ra			# return from two

Download two_powerful.s

Integers
print digits from an integer one per line, reverse order
#include <stdio.h>

int main(int argc, char *argv[]) {
    int num;
    int rem;

    while (1) {  // forever
        // get the number
        printf("Integer? ");
        if (scanf("%d", &num) != 1) break;

        // extract the digits
        rem = num;
        do {
            printf("%d\n", rem % 10);
            rem = rem / 10;
        } while (rem != 0);
    }
    return 0;
}

Download digits.c

print bits from an integer one per line, reverse order
#include <stdio.h>

#define MAXBITS 32

int main(int argc, char *argv[]) {
    int num;
    unsigned int rem;
    int bits[MAXBITS];
    int nbits;

    while (1) {  // forever
        // get the number
        printf("Integer? ");
        if (scanf("%d", &num) != 1) break;

        // extract the digits
        rem = num;
        nbits = 0;
        do {
            bits[nbits] = rem % 2;
            nbits++;
            rem = rem / 2;
        } while (rem != 0);

        printf("%d = %08x = ", num, num);
        for (int i = nbits-1; i >= 0; i--) {
            printf("%d", bits[i]);
        }
        putchar('\n');
    }
    return 0;
}

Download bits.c



Print size and min and max values of integer types
#include <stdio.h>
#include <limits.h>

int main(void) {

    char c;
    signed char sc;
    unsigned char uc;
    short s;
    unsigned short us;
    int i;
    unsigned int ui;
    long l;
    unsigned long ul;
    long long ll;
    unsigned long long ull;

    printf("%18s %5s %4s\n", "Type", "Bytes", "Bits");

    printf("%18s %5lu %4lu\n", "char",               sizeof c,   8 * sizeof c);

    printf("%18s %5lu %4lu\n", "signed char",        sizeof sc,  8 * sizeof sc);
    printf("%18s %5lu %4lu\n", "unsigned char",      sizeof uc,  8 * sizeof uc);

    printf("%18s %5lu %4lu\n", "short",              sizeof s,   8 * sizeof s);
    printf("%18s %5lu %4lu\n", "unsigned short",     sizeof us,  8 * sizeof us);

    printf("%18s %5lu %4lu\n", "int",                sizeof i,   8 * sizeof i);
    printf("%18s %5lu %4lu\n", "unsigned int",       sizeof ui,  8 * sizeof ui);

    printf("%18s %5lu %4lu\n", "long",               sizeof l,   8 * sizeof l);
    printf("%18s %5lu %4lu\n", "unsigned long",      sizeof ul,  8 * sizeof ul);

    printf("%18s %5lu %4lu\n", "long long",          sizeof ll,  8 * sizeof ll);
    printf("%18s %5lu %4lu\n", "unsigned long long", sizeof ull, 8 * sizeof ull);

    printf("\n");

    printf("%18s %20s %20s\n", "Type", "Min", "Max");

#ifdef __CHAR_UNSIGNED__
    printf("%18s %20hhu %20hhu\n", "char",               (char)CHAR_MIN,         (char)CHAR_MAX);
#else
    printf("%18s %20hhd %20hhd\n", "char",               (char)CHAR_MIN,         (char)CHAR_MAX);
#endif

    printf("%18s %20hhd %20hhd\n", "signed char",        (signed char)SCHAR_MIN, (signed char)SCHAR_MAX);
    printf("%18s %20hhu %20hhu\n", "unsigned char",      (unsigned char)0,       (unsigned char)UCHAR_MAX);

    printf("%18s %20hd %20hd\n",   "short",              (short)SHRT_MIN,        (short)SHRT_MAX);
    printf("%18s %20hu %20hu\n",   "unsigned short",     (unsigned short)0,      (unsigned short)USHRT_MAX);

    printf("%18s %20d %20d\n",     "int",                INT_MIN,                INT_MAX);
    printf("%18s %20u %20u\n",     "unsigned int",       (unsigned int)0,        UINT_MAX);

    printf("%18s %20ld %20ld\n",   "long",               LONG_MIN,               LONG_MAX);
    printf("%18s %20lu %20lu\n",   "unsigned long",      (unsigned long)0,       ULONG_MAX);

    printf("%18s %20lld %20lld\n", "long long",          LLONG_MIN,              LLONG_MAX);
    printf("%18s %20llu %20llu\n", "unsigned long long", (unsigned long long)0,  ULLONG_MAX);

    return 0;
}

Download integer_types.c


example declarations of the most commonly used fixed width integer types found in stdint.h
#include <stdint.h>

int main(void) {

                 // range of values for type
                 //             minimum               maximum
    int8_t   i1; //                 -128                  127
    uint8_t  i2; //                    0                  255
    int16_t  i3; //               -32768                32767
    uint16_t i4; //                    0                65535
    int32_t  i5; //          -2147483648           2147483647
    uint32_t i6; //                    0           4294967295
    int64_t  i7; // -9223372036854775808  9223372036854775807
    uint64_t i8; //                    0 18446744073709551615

    return 0;
}

Download stdint.c


#include <stdio.h>

int main(void) {

    // Common C bug:

    char c;  // c should be declared int   (int16_t would work, int is better)
    while ((c = getchar()) != EOF) {
        putchar(c);
    }

    // Typically `stdio.h` contains:
    // ```c
    // #define EOF -1
    // ```
    //
    // - most platforms: char is signed (-128..127)
    //   - loop will incorrectly exit for a byte containing 0xFF
    //
    // - rare platforms: char is unsigned (0..255)
    //   - loop will never exit

    return 0;
}

Download char_bug.c

two useful functions that we will use in a number of following programs
#include <stdio.h>
#include <stdint.h>

#include "print_bits.h"

// extract the nth bit from a value
int get_nth_bit(uint64_t value, int n) {
    // shift the bit right n bits
    // this leaves the n-th bit as the least significant bit
    uint64_t shifted_value = value >> n;

    // zero all bits except the the least significant bit
    int bit = shifted_value & 1;

    return bit;
}

// print the bottom how_many_bits bits of value
void print_bits(uint64_t value, int how_many_bits) {
    // print bits from most significant to least significant

    for (int i = how_many_bits - 1; i >= 0; i--) {
        int bit = get_nth_bit(value, i);
        printf("%d", bit);
    }
}

Download print_bits.c


print the bits of an int, for example:
```
$ dcc print_bits_of_int.c print_bits.c -o print_bits_of_int
$ ./print_bits_of_int

Enter an int: 42 00000000000000000000000000101010 $ ./print_bits_of_int
Enter an int: -42 11111111111111111111111111010110 $ ./print_bits_of_int
Enter an int: 0 00000000000000000000000000000000 $ ./print_bits_of_int
Enter an int: 1 00000000000000000000000000000001 $ ./print_bits_of_int
Enter an int: -1 11111111111111111111111111111111 $ ./print_bits_of_int
Enter an int: 2147483647 01111111111111111111111111111111 $ ./print_bits_of_int
Enter an int: -2147483648 10000000000000000000000000000000 $ ```

#include <stdio.h>
#include <stdint.h>
#include "print_bits.h"

int main(void) {
    int a = 0;
    printf("Enter an int: ");
    scanf("%d", &a);

    // sizeof returns number of bytes, a byte has 8 bits
    int n_bits = 8 * sizeof a;

    print_bits(a, n_bits);
    printf("\n");

    return 0;
}

Download print_bits_of_int.c


print the twos-complement representation of 8 bit signed integers essentially all modern machines represent integers in
```
$ dcc 8_bit_twos_complement.c print_bits.c -o 8_bit_twos_complement
$ ./8_bit_twos_complement
-128 10000000
-127 10000001
-126 10000010
-125 10000011
-124 10000100
-123 10000101
-122 10000110
-121 10000111
-120 10001000
-119 10001001
-118 10001010
-117 10001011
-116 10001100
-115 10001101
-114 10001110
-113 10001111
-112 10010000
-111 10010001
-110 10010010
-109 10010011
-108 10010100
-107 10010101
-106 10010110
-105 10010111
-104 10011000
-103 10011001
-102 10011010
-101 10011011
-100 10011100
 -99 10011101
 -98 10011110
 -97 10011111
 -96 10100000
 -95 10100001
 -94 10100010
 -93 10100011
 -92 10100100
 -91 10100101
 -90 10100110
 -89 10100111
 -88 10101000
 -87 10101001
 -86 10101010
 -85 10101011
 -84 10101100
 -83 10101101
 -82 10101110
 -81 10101111
 -80 10110000
 -79 10110001
 -78 10110010
 -77 10110011
 -76 10110100
 -75 10110101
 -74 10110110
 -73 10110111
 -72 10111000
 -71 10111001
 -70 10111010
 -69 10111011
 -68 10111100
 -67 10111101
 -66 10111110
 -65 10111111
 -64 11000000
 -63 11000001
 -62 11000010
 -61 11000011
 -60 11000100
 -59 11000101
 -58 11000110
 -57 11000111
 -56 11001000
 -55 11001001
 -54 11001010
 -53 11001011
 -52 11001100
 -51 11001101
 -50 11001110
 -49 11001111
 -48 11010000
 -47 11010001
 -46 11010010
 -45 11010011
 -44 11010100
 -43 11010101
 -42 11010110
 -41 11010111
 -40 11011000
 -39 11011001
 -38 11011010
 -37 11011011
 -36 11011100
 -35 11011101
 -34 11011110
 -33 11011111
 -32 11100000
 -31 11100001
 -30 11100010
 -29 11100011
 -28 11100100
 -27 11100101
 -26 11100110
 -25 11100111
 -24 11101000
 -23 11101001
 -22 11101010
 -21 11101011
 -20 11101100
 -19 11101101
 -18 11101110
 -17 11101111
 -16 11110000
 -15 11110001
 -14 11110010
 -13 11110011
 -12 11110100
 -11 11110101
 -10 11110110
  -9 11110111
  -8 11111000
  -7 11111001
  -6 11111010
  -5 11111011
  -4 11111100
  -3 11111101
  -2 11111110
  -1 11111111
   0 00000000
   1 00000001
   2 00000010
   3 00000011
   4 00000100
   5 00000101
   6 00000110
   7 00000111
   8 00001000
   9 00001001
  10 00001010
  11 00001011
  12 00001100
  13 00001101
  14 00001110
  15 00001111
  16 00010000
  17 00010001
  18 00010010
  19 00010011
  20 00010100
  21 00010101
  22 00010110
  23 00010111
  24 00011000
  25 00011001
  26 00011010
  27 00011011
  28 00011100
  29 00011101
  30 00011110
  31 00011111
  32 00100000
  33 00100001
  34 00100010
  35 00100011
  36 00100100
  37 00100101
  38 00100110
  39 00100111
  40 00101000
  41 00101001
  42 00101010
  43 00101011
  44 00101100
  45 00101101
  46 00101110
  47 00101111
  48 00110000
  49 00110001
  50 00110010
  51 00110011
  52 00110100
  53 00110101
  54 00110110
  55 00110111
  56 00111000
  57 00111001
  58 00111010
  59 00111011
  60 00111100
  61 00111101
  62 00111110
  63 00111111
  64 01000000
  65 01000001
  66 01000010
  67 01000011
  68 01000100
  69 01000101
  70 01000110
  71 01000111
  72 01001000
  73 01001001
  74 01001010
  75 01001011
  76 01001100
  77 01001101
  78 01001110
  79 01001111
  80 01010000
  81 01010001
  82 01010010
  83 01010011
  84 01010100
  85 01010101
  86 01010110
  87 01010111
  88 01011000
  89 01011001
  90 01011010
  91 01011011
  92 01011100
  93 01011101
  94 01011110
  95 01011111
  96 01100000
  97 01100001
  98 01100010
  99 01100011
 100 01100100
 101 01100101
 102 01100110
 103 01100111
 104 01101000
 105 01101001
 106 01101010
 107 01101011
 108 01101100
 109 01101101
 110 01101110
 111 01101111
 112 01110000
 113 01110001
 114 01110010
 115 01110011
 116 01110100
 117 01110101
 118 01110110
 119 01110111
 120 01111000
 121 01111001
 122 01111010
 123 01111011
 124 01111100
 125 01111101
 126 01111110
 127 01111111
$
```

#include <stdio.h>
#include <stdint.h>
#include "print_bits.h"

int main(void) {

    for (int i = -128; i < 128; i++) {
        printf("%4d ", i);
        print_bits(i, 8);
        printf("\n");
    }

    return 0;
}

Download 8_bit_twos_complement.c

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

int main(void) {
    uint8_t b;
    uint32_t u;

    u = 0x03040506;
    // load first byte of u
    b = *(uint8_t *)&u;
    // prints 6 if little-endian
    // and 3 if big-endian
    printf("%d\n", b);
}

Download endian.c

main:
    lbu  $a0, u      # b = *(uint8_t *)&u;
    li   $v0, 1      # printf("%d", a0);
    syscall

    li   $a0, '\n'   # printf("%c", '\n');
    li   $v0, 11
    syscall

    li   $v0, 0      # return 0
    jr   $ra

    .data
u:
    .word 0x3040506  #u = 0x03040506;

Download endian.s

Bitwise Operations
two useful functions that we will use in a number of following programs
#include <stdio.h>
#include <stdint.h>

#include "print_bits.h"

// extract the nth bit from a value
int get_nth_bit(uint64_t value, int n) {
    // shift the bit right n bits
    // this leaves the n-th bit as the least significant bit
    uint64_t shifted_value = value >> n;

    // zero all bits except the the least significant bit
    int bit = shifted_value & 1;

    return bit;
}

// print the bottom how_many_bits bits of value
void print_bits(uint64_t value, int how_many_bits) {
    // print bits from most significant to least significant

    for (int i = how_many_bits - 1; i >= 0; i--) {
        int bit = get_nth_bit(value, i);
        printf("%d", bit);
    }
}

Download print_bits.c



Demonstrate that shifting the bits of a positive int left 1 position is equivalent to multiplying by 2
```
$ dcc shift_as_multiply.c print_bits.c -o shift_as_multiply
$ ./shift_as_multiply 4
2 to the power of 4 is 16

In binary it is: 00000000000000000000000000010000 $ ./shift_as_multiply 20 2 to the power of 20 is 1048576
In binary it is: 00000000000100000000000000000000 $ ./shift_as_multiply 31 2 to the power of 31 is 2147483648
In binary it is: 10000000000000000000000000000000 $ ```

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include "print_bits.h"

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <exponent>\n", argv[0]);
        return 1;
    }

    int n = strtol(argv[1], NULL, 0);

    uint32_t power_of_two;

    int n_bits = 8 * sizeof power_of_two;

    if (n >= n_bits) {
        fprintf(stderr, "n is too large\n");
        return 1;
    }

    power_of_two = 1;
    power_of_two = power_of_two << n;

    printf("2 to the power of %d is %u\n", n, power_of_two);

    printf("In binary it is: ");
    print_bits(power_of_two, n_bits);
    printf("\n");

    return 0;
}

Download shift_as_multiply.c



Demonstrate use shift operators and subtraction to obtain a bit pattern of n 1s
```
$ dcc set_low_bits.c print_bits.c -o n_ones
$ ./set_low_bits 3

The bottom 3 bits of 7 are ones: 00000000000000000000000000000111 $ ./set_low_bits 19
The bottom 19 bits of 524287 are ones: 00000000000001111111111111111111 $ ./set_low_bits 29
The bottom 29 bits of 536870911 are ones: 00011111111111111111111111111111 ```

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include "assert.h"

#include "print_bits.h"

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <exponent>\n", argv[0]);
        return 1;
    }

    int n = strtol(argv[1], NULL, 0);

    uint32_t mask;

    int n_bits = 8 * sizeof mask;

    assert(n >= 0 && n < n_bits);

    mask = 1;
    mask = mask << n;
    mask = mask - 1;

    printf("The bottom %d bits of %u are ones:\n", n, mask);
    print_bits(mask, n_bits);
    printf("\n");

    return 0;
}

Download set_low_bits.c



Demonstrate use shift operators and subtraction to obtain a bit pattern with a range of bits set.
```
$ dcc set_bit_range.c print_bits.c -o set_bit_range
$ ./set_bit_range 0 7

Bits 0 to 7 of 255 are ones: 00000000000000000000000011111111 $ ./set_bit_range 8 15
Bits 8 to 15 of 65280 are ones: 00000000000000001111111100000000 $ ./set_bit_range 8 23
Bits 8 to 23 of 16776960 are ones: 00000000111111111111111100000000 $ ./set_bit_range 1 30
Bits 1 to 30 of 2147483646 are ones: 01111111111111111111111111111110 ```

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include "print_bits.h"

int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <low-bit> <high-bit>\n", argv[0]);
        return 1;
    }

    int low_bit = strtol(argv[1], NULL, 0);
    int high_bit = strtol(argv[2], NULL, 0);

    uint32_t mask;

    int n_bits = 8 * sizeof mask;

    assert(low_bit >= 0);
    assert(high_bit >= low_bit);
    assert(high_bit < n_bits);

    int mask_size = high_bit - low_bit + 1;

    mask = 1;
    mask = mask << mask_size;
    mask = mask - 1;
    mask = mask << low_bit;

    printf("Bits %d to %d of %u are ones:\n", low_bit, high_bit, mask);
    print_bits(mask, n_bits);
    printf("\n");

    return 0;
}

Download set_bit_range.c



Demonstrate use shift operators and subtraction to extract a bit pattern with a range of bits set.
```
$ dcc extract_bit_range.c print_bits.c -o extract_bit_range
$ ./extract_bit_range 4 7 42

Value 42 in binary is: 00000000000000000000000000101010
Bits 4 to 7 of 42 are: 0010 $ ./extract_bit_range 10 20 123456789
Value 123456789 in binary is: 00000111010110111100110100010101
Bits 10 to 20 of 123456789 are: 11011110011 ```

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include "print_bits.h"

int main(int argc, char *argv[]) {
    if (argc != 4) {
        fprintf(stderr, "Usage: %s <low-bit> <high-bit> <value>\n", argv[0]);
        return 1;
    }

    int low_bit = strtol(argv[1], NULL, 0);
    int high_bit = strtol(argv[2], NULL, 0);
    uint32_t value = strtol(argv[3], NULL, 0);

    uint32_t mask;

    int n_bits = 8 * sizeof mask;

    assert(low_bit >= 0);
    assert(high_bit >= low_bit);
    assert(high_bit < n_bits);

    int mask_size = high_bit - low_bit + 1;

    mask = 1;
    mask = mask << mask_size;
    mask = mask - 1;
    mask = mask << low_bit;

    // get a value with the bits outside the range low_bit..high_bit set to zero
    uint32_t extracted_bits = value & mask;

    // right shift the extracted_bits so low_bit becomes bit 0
    extracted_bits = extracted_bits >> low_bit;

    printf("Value %u in binary is:\n", value);
    print_bits(value, n_bits);
    printf("\n");

    printf("Bits %d to %d of %u are:\n", low_bit, high_bit, value);
    print_bits(extracted_bits, mask_size);
    printf("\n");

    return 0;
}

Download extract_bit_range.c



Convert an integer to a string of hexadecimal digits without using snprintf to demonstrate using bitwise operators to extract digits
```
$ dcc int_to_hex_string.c -o int_to_hex_string
$ ./int_to_hex_string
$ ./int_to_hex_string

Enter a positive int: 42 42 = 0x0000002A $ ./int_to_hex_string
Enter a positive int: 65535 65535 = 0x0000FFFF $ ./int_to_hex_string
Enter a positive int: 3735928559 3735928559 = 0xDEADBEEF $ ```

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

char *int_to_hex_string(uint32_t n);

int main(void) {
    uint32_t a = 0;
    printf("Enter a positive int: ");
    scanf("%u", &a);

    char *hex_string = int_to_hex_string(a);

    // print the returned string
    printf("%u = 0x%s\n", a, hex_string);

    free(hex_string);

    return 0;
}

// return a malloced string containing the hexadecimal digits of n

char *int_to_hex_string(uint32_t n) {
    // sizeof returns number of bytes in n's representation
    // each byte is 2 hexadecimal digits

    int n_hex_digits = 2 * (sizeof n);

    // allocate memory to hold the hex digits + a terminating 0
    char *string = malloc(n_hex_digits + 1);

    // print hex digits from most significant to least significant

    for (int which_digit = 0; which_digit < n_hex_digits; which_digit++) {
        // shift value across so hex digit we want
        // is in bottom 4 bits

        int bit_shift = 4 * which_digit;
        uint32_t shifted_value = n >> bit_shift;

        // mask off (zero) all bits but the bottom 4 bites

        int hex_digit = shifted_value & 0xF;

        // hex digit will be a value 0..15
        // obtain the corresponding ASCII value
        // "0123456789ABCDEF" is a char array
        // containing the appropriate ASCII values

        int hex_digit_ascii = "0123456789ABCDEF"[hex_digit];

        int string_position = n_hex_digits - which_digit - 1;
        string[string_position] = hex_digit_ascii;
    }

    // 0 terminate the array
    string[n_hex_digits] = 0;

    return string;
}

Download int_to_hex_string.c



Convert a hexadecimal string to an integer
```
$ dcc hex_string_to_int.c -o hex_string_to_int
$ dcc hex_string_to_int.c -o hex_string_to_int
$ ./hex_string_to_int  2A
2A hexadecimal is 42 base 10
$ ./hex_string_to_int FFFF

FFFF hexadecimal is 65535 base 10 $ ./hex_string_to_int DEADBEEF
DEADBEEF hexadecimal is 3735928559 base 10 $ ```

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

uint32_t hex_string_to_int(char *hex_string);
int hex_digit_to_int(int ascii_digit);

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <hexadecimal-number>\n", argv[0]);
        return 1;
    }

    char *hex_string = argv[1];

    uint32_t u = hex_string_to_int(hex_string);

    printf("%s hexadecimal is %u base 10\n", hex_string, u);

    return 0;
}

uint32_t hex_string_to_int(char *hex_string) {
    uint32_t value = 0;

    for (int i = 0; hex_string[i] != 0; i++) {
        int ascii_hex_digit = hex_string[i];
        int digit_as_int = hex_digit_to_int(ascii_hex_digit);

        value = value << 4;
        value = value | digit_as_int;
    }

    return value;
}

// given the ascii value of a hexadecimal digit
// return the corresponding integer

int hex_digit_to_int(int ascii_digit) {
    if (ascii_digit >= '0' && ascii_digit <= '9') {
        // the ASCII characters '0' .. '9' are contiguous
        // in other words they have consecutive values
        // so subtract the ASCII value for '0' yields the corresponding integer

        return ascii_digit - '0';
    }

    if (ascii_digit >= 'A' && ascii_digit <= 'F') {
        // for characters 'A' .. 'F' obtain the
        // corresponding integer for a hexadecimal digit

        return 10 + (ascii_digit - 'A');
    }

    fprintf(stderr, "Bad digit '%c'\n", ascii_digit);
    exit(1);
}

Download hex_string_to_int.c



Examples of illegal bit-shift operations
#include <stdio.h>
#include <stdint.h>

int main(void) {
    // int16_t is a signed type (-32768..32767)
    // below operations are undefined for a signed type
    int16_t i;

    i = -1;
    i = i >> 1; // undefined -  shift of a negative value
    printf("%d\n", i);

    i = -1;
    i = i << 1; // undefined -  shift of a negative value
    printf("%d\n", i);

    i = 32767;
    i = i << 1; // undefined -  left shift produces a negative value

    uint64_t j;
    j = 1 << 33; // undefined - constant 1 is an int
    j = ((uint64_t)1) << 33; // ok

    return 0;
}

Download shift_bug.c

copy stdin to stdout xor'ing each byte with value given as argument
```
$ echo Hello Andrew|xor 42
bOFFE
kDNXO] $ echo Hello Andrew|xor 42|cat -A
bOFFE$
kDNXO] $
$  echo Hello |xor 42
bOFFE $ echo -n 'bOFFE '|xor 42

Hello $ echo Hello|xor 123|xor 123
Hello $ ```

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <xor-value>\n", argv[0]);
        return 1;
    }

    int xor_value = strtol(argv[1], NULL, 0);

    if (xor_value < 0 || xor_value > 255) {
        fprintf(stderr, "Usage: %s <xor-value>\n", argv[0]);
        return 1;
    }

    int c;
    while ((c = getchar()) != EOF) {
        //    exclusive-or
        //    ^  | 0  1
        //   ----|-----
        //    0  | 0  1
        //    1  | 1  0

        int xor_c = c ^ xor_value;

        putchar(xor_c);
    }

    return 0;
}

Download xor.c



Represent a small set of possible values using bits
```
$ dcc pokemon.c print_bits.c -o pokemon
$ ./pokemon
0000010000000000 BUG_TYPE
0000000000010000 POISON_TYPE
1000000000000000 FAIRY_TYPE
1000010000010000 our_pokemon type (1)

Poisonous 1001010000000000 our_pokemon type (2)
Scary ```

#include <stdio.h>
#include <stdint.h>
#include "print_bits.h"


#define POKEMON_TYPE_BITS 16

#define FIRE_TYPE      0x0001
#define FIGHTING_TYPE  0x0002
#define WATER_TYPE     0x0004
#define FLYING_TYPE    0x0008
#define POISON_TYPE    0x0010
#define ELECTRIC_TYPE  0x0020
#define GROUND_TYPE    0x0040
#define PSYCHIC_TYPE   0x0080
#define ROCK_TYPE      0x0100
#define ICE_TYPE       0x0200
#define BUG_TYPE       0x0400
#define DRAGON_TYPE    0x0800
#define GHOST_TYPE     0x1000
#define DARK_TYPE      0x2000
#define STEEL_TYPE     0x4000
#define FAIRY_TYPE     0x8000

int main(void) {

    // example code to create a pokemon with 3 types

    uint16_t our_pokemon = BUG_TYPE | POISON_TYPE | FAIRY_TYPE;

    print_bits(BUG_TYPE, POKEMON_TYPE_BITS);
    printf(" BUG_TYPE\n");
    print_bits(POISON_TYPE, POKEMON_TYPE_BITS);
    printf(" POISON_TYPE\n");
    print_bits(FAIRY_TYPE, POKEMON_TYPE_BITS);
    printf(" FAIRY_TYPE\n");

    print_bits(our_pokemon, POKEMON_TYPE_BITS);
    printf(" our_pokemon type (1)\n");

    // example code to check if a pokemon is of a type:

    if (our_pokemon & POISON_TYPE) {
        printf("Poisonous\n"); // prints
    }

    if (our_pokemon & GHOST_TYPE) {
        printf("Scary\n"); // does not print
    }

    // example code to add a type to a pokemon
    our_pokemon |= GHOST_TYPE;

    // example code to remove a type from a pokemon
    our_pokemon &= ~ POISON_TYPE;

    print_bits(our_pokemon, POKEMON_TYPE_BITS);
    printf(" our_pokemon type (2)\n");

    if (our_pokemon & POISON_TYPE) {
        printf("Poisonous\n"); // does not print
    }

    if (our_pokemon & GHOST_TYPE) {
        printf("Scary\n"); // prints
    }
    return 0;
}

Download pokemon.c


Represent set of small non-negative integers using bit-operations
```
$ dcc bitset.c print_bits.c -o bitset
$ ./bitset

Set members can be 0-63, negative number to finish
Enter set a: 1 2 4 8 16 32 -1
Enter set b: 5 4 3 33 -1 a = 0000000000000000000000000000000100000000000000010000000100010110 = 0x100010116 = 4295033110 b = 0000000000000000000000000000001000000000000000000000000000111000 = 0x200000038 = 8589934648 a = {1,2,4,8,16,32} b = {3,4,5,33} a union b = {1,2,3,4,5,8,16,32,33} a intersection b = {4} cardinality(a) = 6 is_member(42, a) = 0 ```

#include <stdio.h>
#include <stdint.h>
#include <assert.h>
#include "print_bits.h"

typedef uint64_t set;

#define MAX_SET_MEMBER ((int)(8 * sizeof(set) - 1))
#define EMPTY_SET 0

set set_add(int x, set a);
set set_union(set a, set b);
set set_intersection(set a, set b);
int set_member(int x, set a);
int set_cardinality(set a);
set set_read(char *prompt);
void set_print(char *description, set a);

void print_bits_hex(char *description, set n);

int main(void) {
    printf("Set members can be 0-%d, negative number to finish\n",
           MAX_SET_MEMBER);
    set a = set_read("Enter set a: ");
    set b = set_read("Enter set b: ");

    print_bits_hex("a = ", a);
    print_bits_hex("b = ", b);
    set_print("a = ", a);
    set_print("b = ", b);
    set_print("a union b = ", set_union(a, b));
    set_print("a intersection b = ", set_intersection(a, b));
    printf("cardinality(a) = %d\n", set_cardinality(a));
    printf("is_member(42, a) = %d\n", (int)set_member(42, a));

    return 0;
}

set set_add(int x, set a) {
    return a | ((set)1 << x);
}

set set_union(set a, set b) {
    return a | b;
}

set set_intersection(set a, set b) {
    return a & b;
}

// return 1 iff x is a member of a, 0 otherwise
int set_member(int x, set a) {
    assert(x >= 0 && x <= MAX_SET_MEMBER);
    return (a >> x) & 1;
}

// return size of set
int set_cardinality(set a) {
    int n_members = 0;
    while (a != 0) {
        n_members += a & 1;
        a >>= 1;
    }
    return n_members;
}

set set_read(char *prompt) {
    printf("%s", prompt);
    set a = EMPTY_SET;
    int x;
    while (scanf("%d", &x) == 1 && x >= 0) {
        a = set_add(x, a);
    }
    return a;
}

// print out member of the set in increasing order
// for example {5,11,56}
void set_print(char *description, set a) {
    printf("%s", description);
    printf("{");
    int n_printed = 0;
    for (int i = 0; i <= MAX_SET_MEMBER; i++) {
        if (set_member(i, a)) {
            if (n_printed > 0) {
                printf(",");
            }
            printf("%d", i);
            n_printed++;
        }
    }
    printf("}\n");
}

// print description then binary, hex and decimal representation of value
void print_bits_hex(char *description, set value) {
    printf("%s", description);
    print_bits(value, 8 * sizeof value);
    printf(" = 0x%08jx = %jd\n", (intmax_t)value, (intmax_t)value);
}

Download bitset.c

Floating Point

Print size and min and max values of floating point types
```
$ ./floating_types
float        4 bytes  min=1.17549e-38   max=3.40282e+38
double       8 bytes  min=2.22507e-308  max=1.79769e+308
long double 16 bytes  min=3.3621e-4932  max=1.18973e+4932
```

#include <stdio.h>
#include <float.h>

int main(void) {

    float f;
    double d;
    long double l;
    printf("float       %2lu bytes  min=%-12g  max=%g\n", sizeof f, FLT_MIN, FLT_MAX);
    printf("double      %2lu bytes  min=%-12g  max=%g\n", sizeof d, DBL_MIN, DBL_MAX);
    printf("long double %2lu bytes  min=%-12Lg  max=%Lg\n", sizeof l, LDBL_MIN, LDBL_MAX);

    return 0;
}

Download floating_types.c


#include <stdio.h>
#include <math.h>

int main(void) {

    double x = 1.0/0.0;

    printf("%lf\n", x); //prints inf

    printf("%lf\n", -x); //prints -inf

    printf("%lf\n", x - 1); // prints inf

    printf("%lf\n", 2 * atan(x)); // prints 3.141593

    printf("%d\n", 42 < x); // prints 1 (true)

    printf("%d\n", x == INFINITY); // prints 1 (true)

    return 0;
}

Download infinity.c


#include <stdio.h>
#include <math.h>

int main(void) {

    double x = 0.0/0.0;

    printf("%lf\n", x); //prints nan

    printf("%lf\n", x - 1); // prints nan

    printf("%d\n", x == x); // prints 0 (false)

    printf("%d\n", isnan(x)); // prints 1 (true)

    return 0;
}

Download nan.c



The value 0.1 can not be precisely represented as a double
As a result b != 0
#include <stdio.h>

int main(void) {
    double a, b;

    a = 0.1;
    b = 1 - (a + a + a + a + a + a + a + a + a + a);

    if (b != 0) {  // better would be fabs(b) > 0.000001
        printf("1 != 0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1\n");
    }

    printf("b = %g\n", b); // prints 1.11022e-16

    return 0;
}

Download double_imprecision.c


- 9007199254740993 is $2^{53} + 1$ \
  it is smallest integer which can not be represented exactly as a double
- The closest double to 9007199254740993 is 9007199254740992.0
- aside: 9007199254740993 can not be represented by a int32_t \
  it can be represented by int64_t

#include <stdio.h>

int main(void) {


    // loop looks to print 10 numbers but actually never terminates
    double d = 9007199254740990;
    while (d < 9007199254741000) {
        printf("%lf\n", d); // always prints 9007199254740992.000000

        // 9007199254740993 can not be represented as a double
        // closest double is 9007199254740992.0
        // so 9007199254740992.0 + 1 = 9007199254740992.0
        d = d + 1;
    }

    return 0;
}

Download double_disaster.c

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

/*```
$ dcc double_not_always.c -o double_not_always
$ ./double_not_always 42.3
d = 42.3
d == d is true
d == d + 1 is false
$  ./double_not_always 4200000000000000000
d = 4.2e+18
d == d is true
d == d + 1 is true
$ ./double_not_always NaN
d = nan
d == d is not true
d == d + 1 is false
````*/

int main(int argc, char *argv[]) {
    assert(argc == 2);

    double d = strtod(argv[1], NULL);

    printf("d = %g\n", d);

    if (d == d) {
        printf("d == d is true\n");
    } else {
        // will be executed if d is a NaN
        printf("d == d is not true\n");
    }

    if (d == d + 1) {
        // may be executed if d is large
        // because closest possible representation for d + 1
        // is also closest possible representation for d
        printf("d == d + 1 is true\n");
    } else {
        printf("d == d + 1 is false\n");
    }

    return 0;
}

Download double_not_always.c



Print the underlying representation of a float
The float can be supplied as a decimal or a bit-string
$ dcc explain_float_representation.c -o explain_float_representation
$ ./explain_float_representation

0.15625 is represented in IEEE-754 single-precision by these bits:
00111110001000000000000000000000
sign | exponent | fraction 0 | 01111100 | 01000000000000000000000
sign bit = 0 sign = +
raw exponent = 01111100 binary = 124 decimal actual exponent = 124 - exponent_bias = 124 - 127 = -3
number = +1.01000000000000000000000 binary * 2**-3 = 1.25 decimal * 2**-3 = 1.25 * 0.125 = 0.15625
$ ./explain_float_representation -0.125
-0.125 is represented as a float (IEEE-754 single-precision) by these bits:
10111110000000000000000000000000
sign | exponent | fraction 1 | 01111100 | 00000000000000000000000
sign bit = 1 sign = -
raw exponent = 01111100 binary = 124 decimal actual exponent = 124 - exponent_bias = 124 - 127 = -3
number = -1.00000000000000000000000 binary * 2**-3 = -1 decimal * 2**-3 = -1 * 0.125 = -0.125
$ ./explain_float_representation 150.75
150.75 is represented in IEEE-754 single-precision by these bits:
01000011000101101100000000000000
sign | exponent | fraction 0 | 10000110 | 00101101100000000000000
sign bit = 0 sign = +
raw exponent = 10000110 binary = 134 decimal actual exponent = 134 - exponent_bias = 134 - 127 = 7
number = +1.00101101100000000000000 binary * 2**7 = 1.17773 decimal * 2**7 = 1.17773 * 128 = 150.75
$ ./explain_float_representation -96.125
-96.125 is represented in IEEE-754 single-precision by these bits:
11000010110000000100000000000000
sign | exponent | fraction 1 | 10000101 | 10000000100000000000000
sign bit = 1 sign = -
raw exponent = 10000101 binary = 133 decimal actual exponent = 133 - exponent_bias = 133 - 127 = 6
number = -1.10000000100000000000000 binary * 2**6 = -1.50195 decimal * 2**6 = -1.50195 * 64 = -96.125
$ ./explain_float_representation inf
inf is represented in IEEE-754 single-precision by these bits:
01111111100000000000000000000000
sign | exponent | fraction 0 | 11111111 | 00000000000000000000000
sign bit = 0 sign = +
raw exponent = 11111111 binary = 255 decimal number = +inf
$ ./explain_float_representation 00111101110011001100110011001101 sign bit = 0 sign = +
raw exponent = 01111011 binary = 123 decimal actual exponent = 123 - exponent_bias = 123 - 127 = -4
number = +1.10011001100110011001101 binary * 2**-4 = 1.6 decimal * 2**-4 = 1.6 * 0.0625 = 0.1
$ ./explain_float_representation 01111111110000000000000000000000 sign bit = 0 sign = +
raw exponent = 11111111 binary = 255 decimal number = NaN $ ```

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <float.h>
#include <string.h>

void display_float(char *argument);
uint32_t get_float_bits(float f);
void print_float_bits(uint32_t bits);
void print_bit_range(uint32_t value, int high, int low);
void print_float_details(uint32_t bits);
uint32_t extract_bit_range(uint32_t value, int high, int low);
uint32_t convert_bitstring_to_uint32(char *bit_string);

int main(int argc, char *argv[]) {
    for (int arg = 1; arg < argc; arg++) {
        display_float(argv[arg]);
    }
    return 0;
}

// Define the constants used in representation of a float in IEEE 754 single-precision
// https://en.wikipedia.org/wiki/Single-precision_floating-point_format
// explains format

#define N_BITS             32
#define SIGN_BIT           31
#define EXPONENT_HIGH_BIT  30
#define EXPONENT_LOW_BIT   23
#define FRACTION_HIGH_BIT  22
#define FRACTION_LOW_BIT    0

#define EXPONENT_OFFSET   127
#define EXPONENT_INF_NAN  255

void display_float(char *argument) {
    uint32_t bits;

    // is this argument a bit string or a float?
    if (strlen(argument) > N_BITS - 4 && strspn(argument, "01") == N_BITS) {
        bits = convert_bitstring_to_uint32(argument);
    } else {
        float number = strtof(argument, NULL);
        bits = get_float_bits(number);
        printf("\n%s is represented as IEEE-754 single-precision by these bits:\n\n", argument);
        print_float_bits(bits);
    }

    print_float_details(bits);
}

void print_float_details(uint32_t bits) {
    uint32_t sign_bit = extract_bit_range(bits, SIGN_BIT, SIGN_BIT);
    uint32_t fraction_bits = extract_bit_range(bits, FRACTION_HIGH_BIT, FRACTION_LOW_BIT);
    uint32_t exponent_bits = extract_bit_range(bits, EXPONENT_HIGH_BIT, EXPONENT_LOW_BIT);

    int sign_char, sign_value;

    if (sign_bit == 1) {
        sign_char = '-';
        sign_value = -1;
    } else {
        sign_char = '+';
        sign_value = 1;
    }

    int exponent = exponent_bits - EXPONENT_OFFSET;

    printf("sign bit = %d\n", sign_bit);
    printf("sign = %c\n\n", sign_char);
    printf("raw exponent    = ");
    print_bit_range(bits, EXPONENT_HIGH_BIT, EXPONENT_LOW_BIT);
    printf(" binary\n");
    printf("                = %d decimal\n", exponent_bits);

    int implicit_bit = 1;

    // handle special cases of +infinity, -infinity
    // and Not a Number (NaN)
    if (exponent_bits == EXPONENT_INF_NAN) {
        if (fraction_bits == 0) {
            printf("number = %cinf\n\n", sign_char);
        } else {
            // https://en.wikipedia.org/wiki/NaN
            printf("number = NaN\n\n");
        }
        return;
    }

    if (exponent_bits == 0) {
        // if the exponent_bits are zero its a special case
        // called a denormal number
        // https://en.wikipedia.org/wiki/Denormal_number
        implicit_bit = 0;
        exponent++;
    }

    printf("actual exponent = %d - exponent_bias\n", exponent_bits);
    printf("                = %d - %d\n", exponent_bits, EXPONENT_OFFSET);
    printf("                = %d\n\n", exponent);

    printf("number = %c%d.", sign_char, implicit_bit);
    print_bit_range(bits, FRACTION_HIGH_BIT, FRACTION_LOW_BIT);
    printf(" binary * 2**%d\n", exponent);

    int fraction_size = FRACTION_HIGH_BIT - FRACTION_LOW_BIT + 1;
    double fraction_max = ((uint32_t)1) << fraction_size;
    double fraction = implicit_bit + fraction_bits / fraction_max;

    fraction *= sign_value;

    printf("       = %g decimal * 2**%d\n", fraction,  exponent);
    printf("       = %g * %g\n", fraction, exp2(exponent));
    printf("       = %g\n\n", fraction * exp2(exponent));
}

union overlay_float {
    float f;
    uint32_t u;
};

// return the raw bits of a float
uint32_t get_float_bits(float f) {
    union overlay_float overlay;
    overlay.f = f;
    return overlay.u;
}

// print out the bits of a float
void print_float_bits(uint32_t bits) {
    print_bit_range(bits, 8 * sizeof bits - 1, 0);
    printf("\n\n");
    printf("sign | exponent | fraction\n");
    printf("   ");
    print_bit_range(bits, SIGN_BIT, SIGN_BIT);
    printf(" | ");
    print_bit_range(bits, EXPONENT_HIGH_BIT, EXPONENT_LOW_BIT);
    printf(" | ");
    print_bit_range(bits, FRACTION_HIGH_BIT, FRACTION_LOW_BIT);
    printf("\n\n");
}

// print the binary representation of a value
void print_bit_range(uint32_t value, int high, int low) {
    for (int i = high; i >= low; i--) {
        int bit = extract_bit_range(value, i, i);
        printf("%d", bit);
    }
}

// extract a range of bits from a value
uint32_t extract_bit_range(uint32_t value, int high, int low) {
    uint32_t mask = (((uint32_t)1) << (high - low + 1)) - 1;
    return (value >> low) & mask;
}

// given a string of 1s and 0s return the correspong uint32_t
uint32_t convert_bitstring_to_uint32(char *bit_string) {
    uint32_t bits = 0;
    for (int i = 0; i < N_BITS && bit_string[i] != '\0'; i++) {
        int ascii_char = bit_string[N_BITS - 1 - i];
        uint32_t bit = ascii_char != '0';
        bits = bits | (bit << i);
    }
    return bits;
}

Download explain_float_representation.c

Files