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
Course Content Topic-by-Topic
#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;
}
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;
}
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 "
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;
}
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;
}
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;
}
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;
}
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;
#include <stdio.h>
int x, y, z;
int main(void) {
x = 17;
y = 25;
z = x + y;
printf("%d", z);
printf("\n");
return 0;
}
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
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
#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;
}
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}
Simple example of accessing an array element
#include <stdio.h>
int array[10];
int main(void) {
array[3] = 17;
}
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];
#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]);
}
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");
}
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"
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"
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"
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;
}
simple example of returning a value from a function
note storing of return address $ra
code for function main
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 #
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;
}
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 #
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);
}
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
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;
}
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;
}
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;
}
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;
}
#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;
}
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);
}
}
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;
}
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;
}
#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);
}
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;
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);
}
}
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;
}
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;
}
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;
}
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;
}
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;
}
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);
}
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;
}
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;
}
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;
}
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);
}
- 18s2 COMP1521 Lecture Video: Data representation: floats, arrays, structs
- Wikpedia: floating point representation
- Floating point calculator
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;
}
#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;
}
#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;
}
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;
}
- 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;
}
#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;
}
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;
}
All Links
- All Tutorial Questions
- All Tutorial Answers
- All Laboratory Exercises
- All Laboratory Sample Solutions
- All Weekly Test Questions
- All Weekly Test Sample Answers
-
- All Extra Revision Revision Questions
- All Extra Revision Sample Solutions
- Mips Basics
- Mips Control
- Mips Data
- Mips Functions
- Integers
- Bitwise Operations
- Floating Point
- Files
Square two numbers and sum their squares.