Computer Systems Fundamentals

&    U+00026  0010 0110
 
µ    U+000B5  1011 0101    
              11000010     10110101

♥    U+02665  0010 0110 0110 0101
              11100010 10011001 10100101

Reminder:
7bit  0xxxxxxx
11bit 110xxxxx 10xxxxxx
16bit 1110xxxx 10xxxxxx 10xxxxxx
24bit 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
#include <stdio.h>

int main(void) {
    printf("The unicode code point U+1F600 encodes in UTF-8\n");
    printf("as 4 bytes: 0xF0 0x9F 0x98 0x80\n");
    printf("We can output the 4 bytes like this: \xF0\x9F\x98\x80\n");
    printf("Or like this: ");
    putchar(0xF0);
    putchar(0x9F);
    putchar(0x98);
    putchar(0x80);
    putchar('\n');
}
#include <stdio.h>
#include <string.h>
#include <stdint.h>

// UTF-8 encoding of 2665
// 11100010 10011001 10100101
        
// 11100010         10011001        10100101
// E   2            9   9           A   5

int utf8_num_bytes(uint8_t first_byte);

int main(void){ 
    // \u only works for code points with up to 4 hex digits
    printf("\u2665\n");
    
    printf("\xE2\x99\xA5\n");
    char *s = "\u2665 beats";
    printf("%s\n",s);
    printf("♥ beats\n"); //ctrl-shift-u 2665
    
    
    //string functions DO NOT WORK WITH UNICODE
    printf("%lu\n",strlen("\u2665")); //3 not 1
    printf("%lu\n",strlen(s));        //9 not 7
  
    //using our own function to work out the number
    //of bytes in unicode character with a given first byte
    uint8_t first_byte = 0xE2;
    printf("Expected nbytes for \\x%hhX is %d\n",first_byte, utf8_num_bytes(first_byte));
    
    return 0;
}


int utf8_num_bytes(uint8_t first_byte){
    if ((first_byte & 0x80) == 0) {
        return 1;
    } else if ((first_byte & 0xe0) == 0xc0) {
        return 2;
    } else if ((first_byte & 0xf0) == 0xe0) {
        return 3;
    } else if ((first_byte & 0xf8) == 0xf0) {
        return 4;
    } else {
        fprintf(stderr,"Not a valid first byte\n");
        return 0;
    }
}
 
12.625

12/2 = 6 R0
 6/2 = 3 R0
 3/2 = 1 R1
 1/2 = 0 R1

12: 1100

0.625 * 2 = 1.25
0.25  * 2 = 0.5
0.5   * 2 = 1.0

0.625 = 0.101

12.625: 1100.101


=============   

0.1 * 2 = 0.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2

0.0001100110011001100110011001100110011

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>

int main(void) {
    double d = 4/7.0;

    // prints in decimal with (default) 6 decimal places
    printf("%lf\n", d);        // prints 0.571429

    // prints in scientific notation
    printf("%le\n", d);       // prints 5.714286e-01

    // picks best of decimal and scientific notation
    printf("%lg\n", d);       // prints 0.571429

    //  prints in decimal with 9 decimal places
    printf("%.9lf\n", d);    // prints 0.571428571

    //  prints in decimal with 1 decimal place and field width of 5
    printf("%10.1lf\n", d);  // prints        0.6

    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;
}
Convert 1 to floating point representation

1 = 1.0 * 2^0
sign: 0
exp: 0 + 127 = 01111111
frac: 1.0

00111111100000000000000000000000  (1)


========================================
Convert to decimal

0 10000000 11000000000000000000000

sign: +ve
exp: 10000000 = 128 - 127 = 1
frac: 1.11

answer = 1.11*2^1 = 11.1  = 3.5

===========================================
Convert to decimal

1 01111110 10000000000000000000000

sign: -ve
exp: 01111110 = 126 - 127 = -1
frac: 1.1

answer = -1.1*2^-1 = -0.11 = -0.75

================================
Extra example:
                             
0 10000000 10000000000000000000000

sign: +ve
exp: 10000000 = 128 - 127 = 1
fraction: 1.1
result: 1.1 * 2^1 = 11 = 3


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>

int square(int x){
    return x*x;
}
int absVal(int x){
    if( x < 0){
        return -x;
    } 
    return x;
}
int main(int argc, char * argv[]){
    int (*fp)(int);
    fp = &square;
    int n = (*fp)(9); //9*9

    printf("answer is %d\n",n);//81
    //& is optional
    fp = &absVal;
    //8 8 
    printf("answer is %d %d\n",(*fp)(-8),fp(-8));    
    

    printf("%p %p\n",absVal,&absVal);
    printf("%p %p\n",square,&square);
    printf("%p\n",&n);
    return 0;
}
#include <stdio.h>


struct a{
    double p;   //8
    double pq;  //8 
    char c;     //1 
    char d;     //1
                //6
};              // = 24

struct b{
    char c;   //1 byte
    char p;   //1 byte
};            //= 2 bytes

struct c{
    int x;    //4 
    char c;   //1
              //3  
    double f; //8  
    char s[3];//3
              //5
              // = 24 bytes      
};

int main(void){
    printf("size of %ld\n",sizeof(struct a));
    printf("size of %ld\n",sizeof(struct b));
    printf("size of %ld\n",sizeof(struct c));

    return 0;
}
#include <stdio.h>

void printBytes(unsigned char *ptr, int len);

int main(void){
    int x = 1;   // 0x00000001
                 // LITTLE 01 00 00 00
                 // BIG    00 00 00 01
     
    unsigned char * ptr; 
    ptr = (unsigned char *) &x;
    printf("%x %x %x %x\n",*ptr, *(ptr+1),*(ptr+2),*(ptr+3));   
        
    return 0;
}

/**
We write it as 0x00000000  00000000 00000000 00000001


But on our machine is it stored like

0x1000  00000000 BIG
0x1001  00000000
0x1002  00000000
0x1003  00000001

OR

0x1000  00000001  LITTLE
0x1001  00000000
0x1002  00000000
0x1003  00000000


*/
#include <stdio.h>

void printBytes(unsigned char *ptr, int len);

int main(void){
    int x = 0xF2A913EF;   
    short y = 0x1234; //2 bytes
   
    printf("%x %hx\n",x,y);
   
    unsigned char * ptr; 
    ptr = (unsigned char *)&x;
    printf("%x\n",*ptr);
     
    printBytes(ptr,4); //print 4 bytes
    ptr = (unsigned char *)&y;
    printBytes(ptr,2);
    return 0;
}


 
void printBytes(unsigned char *ptr, int len){
    for(int i =0; i < len; i++){
        printf("%x ",*ptr);
        ptr++;
    }
    printf("\n");
}
#include <stdio.h>

//In hex array would have
//61 62 63 64 00


int main(void){
    char bytes[] = "abcd";
    printf("%x %x %x %x\n",bytes[0],bytes[1],
                           bytes[2],bytes[3]);
    unsigned int * x = (unsigned int *) bytes;
    printf("%d %x\n",*x,*x);

    return 0;
}

 
// 61
// 62
// 63
// 64
#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:
    li   $t0, 0x03040506
    la   $t1, u
    sw   $t0, 0($t1) # u = 0x03040506;

    lb   $a0, 0($t1) # 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:
    .space 4