// A collection of six small C programs illustrating various issues // in data representation and type conversions in C // // (taken from a lab exercise in CSCI 330, where the students // were expected to explain the "unusual" aspects of each // program's output) // ------------------------------------------------------------------------- // lab 7 sample solutions // ------------------------------------------------------------------------- // lab7a.c and output #include #include int main() { unsigned int y = UINT_MAX; int x = y; printf("lab7a: y is %u, x is %d\n", y, x); } // resulting output lab7a: y is 4294967295, x is -1 // explanation: The binary representation for the maximum (unsigned) positive int on our system is the 32 bit sequence 1111 1111 1111 1111 1111 1111 1111 1111 If we were to represent this as a signed value (2's complement) value there would be a leading 0, i.e. a 33 bit sequence: 0 1111 1111 1111 1111 1111 1111 1111 1111 When assigning a value that is too large to fit in the target data type, the behaviour is implementation defined (i.e. two different compilers could adopt different approaches and still be compliant with the language standard). In our case, it simply drops the most significant bit(s) that don't fit, retaining the ones that do - i.e. the 32 ones. Now we have a signed int containing all ones, i.e. 0xffffffff, which is the two's complement representation for -1. // ------------------------------------------------------------------------- // lab7b.c and output #include void f(void *p) { int I = *(int*)(p); float F = *(float*)(p); printf("lab7b: I is %d, F is %g\n", I, F); } int main() { int x = 3; f((void*)(&x)); float y = 3; f((void*)(&y)); } // resulting output lab7b: I is 3, F is 4.2039e-45 lab7b: I is 1077936128, F is 3 // explanation: The binary for 3 as an int is 000.....0011, hex 00000003, which is the same as the binary representation for the float value 4.2039e-45. (The smallest positive float value is 1.4013e-45, which has hex representation 00000001.) Similarly, the binary representation for 3.0 as a float is 0100 0000 0100 0000 ... 0000 (hex 40400000), which is the same as the binary representation for 1077936128 as a (base 10) int. Our type-casting back and forth to/from void* is really telling the compiler to treat whatever bit sequence as the new data type, not to attempt any actual "conversion" between the types. // ------------------------------------------------------------------------- // lab7c.c and output #include int main() { double d = 0.1; double sum = 0; printf("lab7c: "); while (sum != 1) { sum += d; printf("%lg ", sum); if (sum > 1.5) break; } printf("\n"); } // resulting output lab7c: 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 1.1 1.2 1.3 1.4 1.5 // explanation: The base-10 value 0.1 cannot be represented as a finite value in binary, hence when we store it as a float or double there is a loss of precision - the value stored is NOT exactly 0.1, it's just a very close approximation. In the loop, when we keep adding the 0.1 values together, they never sum to exactly 1 because of that slight inaccuracy, so the loop never satisfies the (sum != 1) condition. // ------------------------------------------------------------------------- // lab7d.c and output #include #include int main() { int i = INT_MAX/64; float f = i; int j = f; printf("lab7d: i %d, j %d\n", i, j); } // resulting output lab7d: i 33554431, j 33554432 // explanation: INT_MAX in hex is 7FFFFFFF, and when we divide by 64 (i.e. shift right 6) we're losing the bottom 6 bits, i.e. we get 1FFFFFF, requiring 25 bits to represent accurately. In floats, we only have 23 bits for the mantissa, plus the one hidden or implied first bit, giving us at most 24 bits of accuracy. The loss of accuracy is then visible when we copy f back into int j. // ------------------------------------------------------------------------- // lab7e.c and output #include int main() { int x = -3; int y = 3; printf("lab7e: x %d, y %d, x(h) %x, y(h) %x\n", x, y, (unsigned int)(x), (unsigned int)(y)); } // resulting output lab7e: x -3, y 3, x(h) fffffffd, y(h) 3 // explanation: For integers <= MAXINT, the signed and unsigned representations are the same, hence the same result both places where we display y. However, if we have negative integers, or integers > MAXINT, the signed and unsigned representations will differ. In the example above, we can see that the 32 bit pattern for -3 is FFFFFFFD (hex). // ------------------------------------------------------------------------- // lab7f.c and output #include int main() { char ch = '1'; printf("lab7f: %c, %d\n", ch, ch); } // resulting output lab7f: 1, 49 // explanation: If we print a character value as a decimal, rather than a character, we are simply shown its ascii value, in this case value 49 for char '1'.