CSC161 2011S Imperative Problem Solving
[Skip to Body]
Primary:
[Front Door]
[Schedule]
[Piazzza]
-
[Academic Honesty]
[Instructions]
Current:
[Current Outline]
[Current EBoard]
-
[Current Assignment]
[Current Lab]
[Current Reading]
Groupings:
[Assignments]
[Documents]
[EBoards]
[Examples]
[Exams]
[Handouts]
[Labs]
[Outlines]
[Readings]
[Reference]
Related Courses:
[CSC195 2003S (Rebelsky)]
[CSC161 2009F (Coahran)]
[CSC161 2010S (Walker)]
[CSC161 2010F (Rebelsky)]
Misc:
[SamR]
[ISO]
[GNU Coding Standards]
I still saw some problems with what I might call general issues of code cleanliness. Make sure to comment code well. Make sure to choose good variable names. Look at your printout to make sure that it's readable.
Beyond the basic formatting issues, there are some important stylistic issues to consider. One important one is that you should strive to avoid duplicated code. If you find that you're writing code with cut-paste-change, see if you can write a helper procedure to factor out common code.
I would also like to see you spend more time organizing your files. In particular, Makefiles make my life much easier when I'm testing your code. (Makefiles with default targets make my life even easier.)
Topics: Code reading, Commenting, Formatting
Hannah and Harvey Hacker don't particularly like all the extra stuff
they think I make them do when writing code. In particular, they don't
see the point to clear variable names, standard formatting, or comments.
They say Our goal is to write code that works efficiently. Why should
we care about the rest?
Recently, they sent me the following code. As you may note, they've been a bit aggressive in avoiding my recommendations.
void s (int x[], int y, int l[], int k, int r[], int q) { int i; for
(i = 0; i < k; i++) l[i] = x[i]; for (i = k; i < y; i++) r[i-k] = x[i];
} void m (int x[], int y, int l[], int k, int r[], int q) { int a = 0;
int b = 0; int c = 0; while ((a < k) && (b < q)) x[c++] = (l[a] < r[b])
? l[a++] : r[b++]; while (a < k) x[c++] = l[a++]; while (b < q) x[c++] =
r[b++]; } int h (int x[], int y) { int k = y/2; int q = y - k; int l[k];
int r[q]; if (y <= 1) return y; s (x, y, l, k, r, q); h (l, k); h (r,
q); m (x, y, l, k, r, q); return k + q; }
They've even provided a header file (even though they don't use it).
#ifndef __HHH_H__ #define __HHH_H__ int h(int x[], int y); #endif // __HHH_H__
Rewrite the code so that it meets my standards. That is, reformat it, choose good names for variables and functions, use mnemonic constants when appropriate, and add comments to help the reader.
You may find the following command-line tester helpful.
/**
* hhh-ui - a simple UI for the strange code produced by H & H H.
*/
// +---------+--------------------------------------------------------
// | Headers |
// +---------+
#include <stdio.h>
#include <stdlib.h>
#include "hhh.h"
// +---------+--------------------------------------------------------
// | Helpers |
// +---------+
void
print_ints (int nums[], int size)
{
int i; // Index variable
// Special case
if (size == 0)
{
printf ("{}");
return;
}
printf ("{%d", nums[0]);
for (i = 1; i < size; i++)
printf (",%d", nums[i]);
printf ("}");
} // print_ints
// +------+-----------------------------------------------------------
// | Main |
// +------+
int
main (int argc, char *argv[])
{
int i; // Everyone's favorite index
int size = argc-1; // The size of the number array
int nums[size]; // A bunch of numbers
int result; // The result of h.
// Convert arguments to numbers
for (i = 0; i < size; i++)
nums[i] = atoi (argv[i+1]);
// Print preliminary information
printf ("Original array: ");
print_ints (nums, size);
printf ("\n");
printf ("Size: %d\n", size);
// Do the work
result = h (nums, size);
// Print the results
printf ("Result: %d\n", result);
printf ("Final array: ");
print_ints (nums, size);
printf ("\n");
// And we're done
return 0;
} // main
Some quick experimentation suggests that h is a sorting
algorithm. Hence, h should be renamed to something like
sort or, preferably, the name of the sorting routine that
it implements.
What sorting routine does it implement? The first helper function,
s, copies parts of the array into two other arrays.
After splitting, h recurses on those two subarrays.
Finally, it calls m to combine those two subarrays.
The first helper might then be called split and the
second helper might be called merge. As you should recall
from your study of sorting in CSC 151, this routine is mergesort.
So, here's the commented code.
/**
* mergesort.c - An implementation of the legendary merge sort routine.
*/
/**
* Procedure:
* split
* Parameters:
* nums, an array of integers
* size, an integer.
* left, an array of integers
* lsize, an integer
* right, an array of integers
* rsize, an integer
* Purpose:
* Split the first size elements nums into two arrays, left and right.
* Produces:
* [Nothing; Called for the side effects.]
* Preconditions:
* nums contains at least size elements.
* left contains at least lsize elements.
* right contains at least rsize elements.
* size = lsize + rsize.
* Postconditions:
* nums is unmodified
* nums[0..size-1] is a permutation of the combination of
* left[0..lsize-1] and right[0..rsize-1]
*/
void
split (int nums[], int size, int left[], int lsize, int right[], int rsize)
{
int i;
// Copy elements into the left half
for (i = 0; i < lsize; i++)
left[i] = nums[i];
// Copy elements into the right half
for (i = lsize; i < size; i++)
right[i-lsize] = nums[i];
} // split
/**
* Procedure:
* merge
* Parameters:
* nums, an array of integers
* size, an integer.
* left, an array of integers
* lsize, an integer
* right, an array of integers
* rsize, an integer
* Purpose:
* Merge two sorted arrays into a larger sorted array.
* Produces:
* [Nothing; Called for the side effects.]
* Preconditions:
* nums contains at least size elements.
* left contains at least lsize elements.
* right contains at least rsize elements.
* size = lsize + rsize.
* The elements in left are in order. That is, left[i] <= left[i+1]
* for all reasonable i.
* The elements in right are in order. That is, right[i] <= right[i+1]
* for all reasonable i.
* Postconditions:
* left is unmodified
* right is unmodified
* nums[0..size-1] is a permutation of the combination of
* left[0..lsize-1] and right[0..rsize-1]
* The elements in nums are in order. That is, nums[i] <= nums[i+1]
* for all reasonable i.
*/
void
merge (int nums[], int size, int left[], int lsize, int right[], int rsize)
{
int lp = 0; // Pointer into the left array
int rp = 0; // Pointer into the right array
int np = 0; // Pointer into main array
// While elements remain in both subarrays
while ((lp < lsize) && (rp < rsize))
// Copy the smallest remaining element into nums
nums[np++] = (left[lp] < right[rp]) ? left[lp++] : right[rp++];
// Copy all remaining elements from left into nums
while (lp < lsize)
nums[np++] = left[lp++];
// Copy all remaining elements from right into nums
while (rp < rsize)
nums[np++] = right[rp++];
} // merge
/**
* Procedure:
* merge_sort
* Parameters:
* nums, an array of integers
* size, an integer.
* Purpose:
* Sort nums using the merge-sort algorithm.
* Produces:
* result, the size of the array. (A pointless value)
* Preconditions:
* nums contains at least size elements.
* Postconditions:
* nums is a permutation of the original array.
* The elements in nums are in order. That is, nums[i] <= nums[i+1]
* for all reasonable i.
*/
int
merge_sort (int nums[], int size)
{
int lsize = size/2; // The size of the left half
int rsize = size - lsize; // The size of the right half
int left[lsize]; // The left half
int right[rsize]; // The right half
// Arrays with 0 or 1 elements are already sorted.
if (size <= 1)
return size;
// Copy elements into the two subarrays
split (nums, size, left, lsize, right, rsize);
// Sort the two subarrays
merge_sort (left, lsize);
merge_sort (right, rsize);
// And join them back together
merge (nums, size, left, lsize, right, rsize);
// We're done
return size;
} // merge_sort
Please make sure that comments precede the code that they comment on. (The one exception is variables, which we comment to the right.) It should be possible for someone to understand the main ideas of your algorithm by scanning down through the comments. (Ideally, that's also how you write your code: First you write your six P's and unit tests, then you put comments in the body to explain the structure of the algorithm, then you write the code.)
In general, you should use slash-slash comments within your code and
slash-star comments before procedures. Slash-slash comments encourage
you to write less (which is good; internal comments should be short).
Also, if you write most of your internal comments using slash-slash
comments, you can comment out
a chunk of code with slash-star
comments.
While many of you figured out what the code did, you had difficulty
choosing good names. Since the outer routine (h) was
the one that was called, it should have been named sort.
Many of you named the merge routine sort, which is inaccurate.
Topics: Testing, Binary search, Sorting
Bif and Bitsie Binsearch have been studying binary search and the ways in which we might use it. They note that
In order for binary search to work, the array must be in order
from smallest to largest.
Being good students, Bif and Bitsie then write a function to check this condition.
/**
* Procedure:
* is_in_order
* Parameters:
* values[], an array of integers
* size, an integer
* Purpose:
* Determine if the first size values in values are in order.
* Produces:
* in_order, an integer that represents a Boolean
* Preconditions:
* 0 <= size <= length of values
* Postconditions:
* If values[i] <= values[i+1] for all reasonable i, then
* in_order is true (1).
* Otherwise, in_order is false (0).
*/
int
is_in_order (int values[], int size)
{
int i;
for (i = 1; i < size; i++)
if (values[i] >= values[i+1])
return 0;
return 1;
} // is_in_order
Their colleagues, Terry and Terry Tester suggest that while this
function will make some things easier, if
is_in_order is not implemented correctly, then it's
pointless to have it, and it won't ensure that binary search can
be applied. (Of course, you would rarely want to spend the effort checking
if the array is in order, since that's much more expensive than binary
search, but, hey, it's still an important function.)
How do we determine if there are errors? We write unit tests. What do we do if the unit tests fail? We try to figure out why they've failed.
a. Write unit tests for is_in_order.
b. Correct any errors that your unit tests revealed.
You should turn in two files: A file with the revised version of is_in_order and a file with the unit tests.
We'd like to think about a variety of things when testing: Different sized arrays, different kinds of values, etc. Since we have access to the source code, we may have to less testing than we might otherwise (e.g., because we can tell from inspection that the sign of numbers seems unimportant).
We also know that some forms of systematic testing are useful. For example, we might test every permutation of some size array. (Code to automatically generate permutations would be good, but I'm too lazy to write that.)
So, here's my testing code.
/**
* iio-utest: A simple set of unit tests for is_in_order.
*/
// +---------+--------------------------------------------------------
// | Headers |
// +---------+
#include "is-in-order.h"
#include <stdio.h>
#include <limits.h>
// +---------+--------------------------------------------------------
// | Globals |
// +---------+
int tests = 0; // How many tests we've conducted.
int errors = 0; // How many errors wev've encountered
// +---------+--------------------------------------------------------
// | Helpers |
// +---------+
/**
* Procedure:
* print_array
* Parameters:
* a, an array of integers
* size, an integer
* Purpose:
* Print out the first size elements of the array in human-readable
* form (more or less). Does not print a final carriage return.
* Produces:
* [Nothing; Called for the side effect]
* Preconditions:
* a has at least size elements.
* Postconditions:
* a has not been modified.
*/
void
print_array (int a[], int size)
{
int i;
if (size == 0)
{
printf ("{}");
return;
}
printf ("{%d", a[0]);
for (i = 1; i < size; i++)
printf (",%d", a[i]);
printf ("}");
} // print_array
/**
* Procedure:
* test_iio
* Parameters:
* a, an array
* size, an integer
* expected, an integer
* Purpose:
* One test of the is_in_order procedure. Calls is_in_order on the
* array and compares the result to expected, updating information for
* testing.
* Produces:
* [Nothing; Called for the side effect]
* Preconditions:
* See preconditions for is_in_order.
* Postconditions:
* a is unmodified.
* Global variables tests and errors may be updated.
*/
void
test_iio (int a[], int size, int expected)
{
// Increment the number of tests
++tests;
// Run the procedure
int result = is_in_order (a, size);
// If we get an unexpected result
if (result != expected)
{
// Tell the user and update our count of errors
++errors;
printf ("ERROR in is_in_order (");
print_array (a, size);
printf (", %d) EXPECTED: %d, RECEIVED %d\n", size, expected, result);
} // if (result != expected)
} // test
// +------+-----------------------------------------------------------
// | Main |
// +------+
int
main ()
{
// Size 0 arrays
int a0a[0];
test_iio (a0a, 0, 1);
// Size 1 arrays
int a1a[1] = { 1 };
test_iio (a1a, 1, 1);
int a1b[1] = { 2 };
test_iio (a1b, 1, 1);
int a1c[1] = { -1 };
test_iio (a1c, 1, 1);
// Size 2 arrays
int a2a[2] = { 0, 1 };
test_iio (a2a, 2, 1);
int a2b[2] = { 1, 0 };
test_iio (a2b, 2, 0);
int a2c[2] = { 0, 0 };
test_iio (a2c, 2, 1);
// Size 3 arrays
int a3a[3] = { 1, 2, 3 };
test_iio (a3a, 3, 1);
int a3b[3] = { 2, 1, 3 };
test_iio (a3b, 3, 0);
int a3c[3] = { 1, 3, 2 };
test_iio (a3c, 3, 0);
int a3d[3] = { 3, 1, 2 };
test_iio (a3d, 3, 0);
int a3e[3] = { 2, 3, 1 };
test_iio (a3e, 3, 0);
int a3f[3] = { 3, 2, 1 };
test_iio (a3f, 3, 0);
// A few interesting bigger arrays
int a4a[4] = { -3, -2, -1, 0 }; // Negative numbers
test_iio (a4a, 4, 1);
int a4b[4] = { 0, 0, 0, 0 }; // All duplicates
test_iio (a4b, 4, 1);
int a5a[5] = { INT_MIN, INT_MIN/2, 0, INT_MAX/2, INT_MAX };
test_iio (a5a, 5, 1);
int a8a[8] = { 0, 0, 1, 1, 1, 2, 3, 3 }; // Some duplicates
test_iio (a8a, 8, 1);
int a8b[8] = { 0, 0, 1, 2, 1, 2, 3, 3 }; // Some duplicates, mid error
test_iio (a8b, 8, 0);
int a8c[8] = { 5, 0, 1, 1, 1, 2, 3, 3 }; // Some duplicates, left error
test_iio (a8c, 8, 0);
int a8d[8] = { 0, 0, 1, 1, 1, 2, 3, 0 }; // Some duplicates, rt error
test_iio (a8d, 8, 0);
// Print the results
printf ("%d tests conducted.\n", tests);
printf ("%d errors encountered.\n", errors);
// And we're done
return errors;
} // main
Here's the output
ERROR in is_in_order ({0,1}, 2) EXPECTED: 1, RECEIVED 0
ERROR in is_in_order ({1,2,3}, 3) EXPECTED: 1, RECEIVED 0
ERROR in is_in_order ({0,0,0,0}, 4) EXPECTED: 1, RECEIVED 0
ERROR in is_in_order ({-2147483648,-1073741824,0,1073741823,2147483647}, 5) EXPECTED: 1, RECEIVED 0
ERROR in is_in_order ({0,0,1,1,1,2,3,3}, 8) EXPECTED: 1, RECEIVED 0
20 tests conducted.
5 errors encountered.
So, our code is broken. We note that some of the instances with duplicate values failed. Why? Let's turn to the original. Well, it says
if (values[i] >= values[i+1])
return 0;
That means if a value is equal to the next value, we fail. That's easy enough to fix. What happens?
ERROR in is_in_order ({0,1}, 2) EXPECTED: 1, RECEIVED 0
ERROR in is_in_order ({1,0}, 2) EXPECTED: 0, RECEIVED 1
ERROR in is_in_order ({1,2,3}, 3) EXPECTED: 1, RECEIVED 0
ERROR in is_in_order ({0,0,0,0}, 4) EXPECTED: 1, RECEIVED 0
ERROR in is_in_order ({-2147483648,-1073741824,0,1073741823,2147483647}, 5) EXPECTED: 1, RECEIVED 0
ERROR in is_in_order ({0,0,1,1,1,2,3,3}, 8) EXPECTED: 1, RECEIVED 0
20 tests conducted.
6 errors encountered.
Yes, it's always comforting when we try to fix code and the number of errors increases. So, what else could be wrong? Let's look at the bounds of the loop.
for (i = 1; i < size; i++)
Why are we starting at 1? Maybe we should start at 0 so that we compare
values[0] to values[1]. Do we get anything
better?
ERROR in is_in_order ({2}, 1) EXPECTED: 1, RECEIVED 0
ERROR in is_in_order ({0,1}, 2) EXPECTED: 1, RECEIVED 0
ERROR in is_in_order ({1,2,3}, 3) EXPECTED: 1, RECEIVED 0
ERROR in is_in_order ({0,0,0,0}, 4) EXPECTED: 1, RECEIVED 0
ERROR in is_in_order ({-2147483648,-1073741824,0,1073741823,2147483647}, 5) EXPECTED: 1, RECEIVED 0
ERROR in is_in_order ({0,0,1,1,1,2,3,3}, 8) EXPECTED: 1, RECEIVED 0
20 tests conducted.
6 errors encountered.
Still 6 errors, but some of them (okay, one of them) is different. The errors don't give us much clue, so it's time to look at the code a bit more. We'll start with the first error, an array of size 1.
for (i = 0; i < size; i++)
if (values[i] >= values[i+1])
return 0;
Well, i is 0 and size is 1, so we compare
values[0] to values[1]. Whoops!
Two things are clearly wrong. First, we shouldn't be doing any comparisons
for an array of size 1. Second, we're looking at a non-existant value,
values[1]. The upper bound must be wrong. Let's change it
to size-1.
20 tests conducted. 0 errors encountered.
Well, everything passed. We should do a more careful analysis to make sure that it's correct, but it's a good start.
Here's the final code.
#include "is-in-order.h"
int
is_in_order (int values[], int size)
{
int i;
for (i = 0; i < size-1; i++)
if (values[i] > values[i+1])
return 0;
return 1;
} // is_in_order
In grading this, I looked at a few major issues. First, I looked to see
whether you had found all of the errors. A good test suite is likely to
have identified all of them. The goes too far
error is a bit harder,
so even a good test suite might have trouble with that. Next, I looked to
see whether you were creative in your test suite. If you had thorough
tests, I was more lenient on the goes too far
error. If you had fewer
tests, I was less lenient.
A number of you ignored the main idea of unit testing and printed output for every test. As we discussed, wading through pages of "This test passed" makes it much more likely that someone will miss a failed test. You should only print out (a) optional information on failed tests
A few of you put all of your tests in the same file as
is_in_order. Remember ... By separating code into
different files, we make it easier to reuse code.
Some of the nicer systematic tests I saw:
Topics: IEEE floating-point representation, Bitwise operations
Flo and Flint Floater are having trouble understanding IEEE floating point representation and have come to your class mentor for help. In helping them, your mentor has decided that it would be helpful to extract the key parts of a floating point number (sign, exponent, significand).
Write a program that reads in a 32-bit floating-point number (a
float) from the command line and prints out the sign,
exponent, and significand.
% ./ieee-exponent 1 For float 1.000000, the sign is +, the exponent is 0, and the significand is 1.000000. % ./ieee-exponent 2 For float 2.000000, the sign is +, the exponent is 1, and the significand is 1.000000. % ./ieee-exponent 0.5 For float 0.500000, the sign is +, the exponent is -1, and the significand is 1.00000.
As you solve this problem, I would like you to use the following strategy.
float to int without
affecting the bits.
To convert from float to int while preserving the bits, you can use the following procedure. (No, you need not understand how the procedure works.)
int
f2i (float f)
{
return *((int *) &f);
} // f2i
My solution is a bit more involved than most, in part because I tried to keep it as explicit as possible.
bit-utils.h
#ifndef __BIT_UTILS_H__ #define __BIT_UTILS_H__ /** * f2i - Convert a float to an integer with the same bit pattern. */ int f2i (float f); /** * i2f - Convert an integer to a float with the same bit pattern. */ float i2f (int i); /** * ith_bit (n,i) - Get the ith bit of n. * Pre: 0 <= i < 32 */ int ith_bit (int n, int i); /** * zero_bit (n,i) - Zero the ith bit of n, returning the result. * Pre: 0 <= i < 32 */ int zero_bit (int n, int i); #endif // __BIT_UTILS_H__
bit-utils.c
/**
* bit-utils.c - A variety of utilities for working with bitwise values.
*/
// +---------+--------------------------------------------------------
// | Headers |
// +---------+
#include "bit-utils.h"
// +--------------------+---------------------------------------------
// | Exported Functions |
// +--------------------+
int
f2i (float f)
{
return *((int *) &f);
} // f2i
float
i2f (int i)
{
return *((float *) &i);
} // i2f
int
ith_bit (int n, int i)
{
return (n >> i) & 1;
} // ith_bit
int
zero_bit(n, i)
{
// Our mask has 1's everywhere besides at the 0 position.
int mask = ~(1 << i);
return n & mask;
} // zero_bit
ieee.h
#ifndef __IEEE_H__ #define __IEEE_H__ /** * ieee.h - Lots of fun utilities for working with IEEE floating-point * representation. */ /** * Get the sign from the bit representation of a float */ int ieee32_sign (float f); /** * Get the exponent from the bit representation of a float. */ int ieee32_exponent (float f); /** * Get the value of the significand, given a particular value for the hidden * bit. */ float ieee32_significand (float f, int hidden); /** * Describe a floating-point number. */ void describe_float (float f); #endif // __IEEE_H__
ieee.c
/**
* ieee.c
* A library of functions for exploring IEEE floating point numbers.
*/
// +---------+--------------------------------------------------------
// | Headers |
// +---------+
#include <stdio.h>
#include <math.h> // For pow
#include "ieee.h"
#include "bit-utils.h"
// +---------+--------------------------------------------------------
// | Helpers |
// +---------+
/**
* Get the bits for the significand.
*/
int
ieee32_significand_bits (float f)
{
return f2i (f) & 0x7FFFFF; // That's 0000000 01111111 11111111 11111111
} // ieee32_significand_bits
// +--------------------+---------------------------------------------
// | Exported Functions |
// +--------------------+
int
ieee32_sign (float f)
{
return ith_bit (f2i (f), 31);
} // ieee32_sign
int
ieee32_exponent (float f)
{
int exponent;
exponent = f2i (f);
// Drop the left bit
exponent = zero_bit (exponent, 31);
// Shift bits over so that they're in the convenient place
exponent = exponent >> 23;
// Subtract the bias
exponent = exponent - 127;
// And we're done
return exponent;
} // ieee32_exponent
float
ieee32_significand (float f, int hidden)
{
int bits = ieee32_significand_bits (f); // Bits of significand
float result = (float) hidden; // Result we're computing
int i; // The bit we're looking at
float value = 0.5; // The value of that bit.
for (i = 22; i >= 0; i--)
{
// If the bit is set, add its value
if (ith_bit (bits, i))
result += value;
// Move to the next value
value /= 2.0;
} // for
// And we're done
return result;
} // ieee32_significand
float
ieee32_significand_hack (float f, int hidden)
{
int bits = ieee32_significand_bits (f);
if (hidden)
bits = bits | 1 << 23;
return bits / pow (2, 23);
} // ieee32_significand_hack
void
describe_float (float f)
{
int sign; // The sign of the number
int exponent; // The exponent of the number
float significand; // The significand
int small; // Special case: Really small numbers
// Print it
printf ("Exploring floating point number %f:\n", f);
// Get the sign
sign = ieee32_sign (f);
// Get the exponent
exponent = ieee32_exponent (f);
// Handle special values
if (exponent == 128)
{
// Infinities
if (ieee32_significand_bits (f) == 0)
{
if (sign)
printf (" Negative infinity\n");
else
printf (" Positive infinity\n");
} // infinite
else
printf ("Not a number\n");
return;
} // special values
// Determine if it's small
small = exponent == -127;
// Get the significand
significand = ieee32_significand (f, !small);
// Special case: Small numbers
if (exponent == -127)
exponent = -126;
printf (" Sign: %s\n", sign ? "-" : "+");
printf (" Exponent: %d\n", exponent);
printf (" Significand: %f\n", significand);
printf (" Significand2: %f\n", ieee32_significand_hack (f, !small));
} // describe_float
ieee-info.c
/**
* ieee-info num0 num1 num2 ...
* Given floating point numbers, print out information on their parts.
*/
// +---------+--------------------------------------------------------
// | Headers |
// +---------+
#include <stdio.h> // For printf
#include <stdlib.h> // For atof
#include "ieee.h" // For all the fun IEEE utilities
// +------+-----------------------------------------------------------
// | Main |
// +------+
int
main (int argc, char *argv[])
{
int i; // Still my favorite counter variable
float f; // The float we're currently playing with
// Describe each number provided on the command line
for (i = 1; i < argc; i++)
describe_float ((float) atof (argv[i]));
// And we're done
return 0;
} // main
ieee-examples.c
/**
* ieee-examples
* Print out some examples of IEEE representation
*/
// +---------+--------------------------------------------------------
// | Headers |
// +---------+
#include <stdio.h> // For printf
#include <stdlib.h> // For atof
#include "ieee.h" // For all the fun IEEE utilities
#include "bit-utils.h" // For itof
// +------+-----------------------------------------------------------
// | Main |
// +------+
int
main (int argc, char *argv[])
{
float f;
int i;
describe_float (1.0);
describe_float (1.0/0.0);
// A bunch of numbers
f = 7.0;
for (i = 0; i < 14; i++)
{
describe_float (f);
describe_float (-f);
f *= 1115.0;
}
f = .0003;
for (i = 0; i < 15; i++)
{
describe_float (f);
describe_float (-f);
f /= 1231;
}
// And we're done
return 0;
} // main
Many of you did not handle the special cases. I did not penalize you for failing to handle them, but I did not the lack.
Many of you wrote much shorter code, taking advantage of C's ability to immediately parse the bits as an integer.
A few of you wrote extra code to compare your computed result to the input number. That's a nice idea.
A few of you put all the bits into an array for ease of access. If you can get them from the integer to put into the array, you might as well use the code you used to get them from the integer in the first place.
A number of you masked off the significand with some number like 8388607.
That number is not very readable. On the other hand, 0x7fffff is quite
readable for the last 23 bits are 1
.
atoi, RevisitedTopics: Strings, Integers, Library functions
Pam and Pat Parser need to use the atoi function to solve some
homework assignments. Since they feel their understanding of atoi
is incomplete, they've written the following program.
/**
* atoi args
* Applies atoi to each of its parameters and prints out the results.
*/
#include <stdio.h>
#include <stdlib.h>
int
main (int argc, char *argv[])
{
int i;
for (i = 1; i < argc; i++)
printf ("'%s' => %d\n", argv[i], atoi (argv[i]));
return 0;
} // main
When they run it, they get some output that doesn't make sense.
$ ./atoi 23 033 0x44 hello 111111111111 '23' => 23 '033' => 33 '0x44' => 0 'hello' => 0 '111111111111' => 2147483647
As this output suggests, atoi does not seem to work well with
either octal or hexadecimal numbers. It also does not do well with integer
overflow. And it gives no error message if there's an error.
Write a new version of atoi that resolves these problems.
In particular, you should support octal and hexadecimal numbers. If an
error occurs (e.g., overflow, invalid input) you should return
INT_MIN and set the shared variable atoi_error.
Here's a revised version of the Parser's program that takes advantage of this new interface.
/**
* atoi args
* Applies atoi to each of its parameters and prints out the results.
*/
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
extern char *atoi_error;
int
main (int argc, char *argv[])
{
int i;
int result;
for (i = 1; i < argc; i++)
{
result = atoi (argv[i]);
if (result == INT_MIN)
printf ("Could not parse '%s' because '%s'\n", argv[i], atoi_error);
else
printf ("'%s' => %d\n", argv[i], result);
} // for
return 0;
} // main
And here's some simulated output.
$ ./atoi 23 033 0x44 hello 111111111111 '23' => 23 '033' => 27 '0x44' => 68 Could not parse 'hello' because 'not a number' Could not parse '111111111111' because 'integer overflow'
/**
* atoi.c
* An improved atoi library.
*/
// +---------+--------------------------------------------------------
// | Headers |
// +---------+
#include <limits.h> // For INT_MIN
// +---------+--------------------------------------------------------
// | Globals |
// +---------+
char *atoi_error = "";
// +---------+--------------------------------------------------------
// | Helpers |
// +---------+
/**
* Determine whether a character is a base-10 digit.
*/
static int
is_digit_10 (char ch)
{
switch (ch)
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return 1;
default:
return 0;
} // switch
} // is_digit_10
/**
* Determine whether a character is a base 8 digit.
*/
static int
is_digit_8 (char ch)
{
switch (ch)
{
case '8':
case '9':
return 0;
default:
return is_digit_10 (ch);
}
} // is_digit_8
/**
* Determine whether a character is a base-16 digit.
*/
static int
is_digit_16 (char ch)
{
switch (ch)
{
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
return 1;
default:
return is_digit_10 (ch);
}
} // is_digit_16
/**
* Determine the value of a base-16 digit, without the evil K&R hacks.
*/
static int
digit_value_16 (char ch)
{
switch (ch)
{
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'a': return 10;
case 'A': return 10;
case 'b': return 11;
case 'B': return 11;
case 'c': return 12;
case 'C': return 12;
case 'd': return 13;
case 'D': return 13;
case 'e': return 14;
case 'E': return 14;
case 'f': return 15;
case 'F': return 15;
default: return -1;
} // switch
} // digit_value_16
/**
* Base 10 atoi. Based on code from K&R.
*/
static int
atoi_10 (char *a)
{
int result = 0; // The computed result
int sign = 1; // The sign of the result
int i=0; // Counter variable for position
int digit; // The value of the current digit
// Special case: Signed
if (a[0] == '+')
i = 1;
else if (a[0] == '-')
{
i = 1;
sign = -1;
}
// Special case: No digits
if (a[i] == '\0')
{
atoi_error = "missing digits in base-10 number";
return INT_MIN;
}
// Process remaining digits
while (a[i] != '\0')
{
if (! is_digit_10 (a[i]))
{
atoi_error = "invalid digit in base-10 number";
return INT_MIN;
}
digit = digit_value_16 (a[i]);
if (result > (INT_MAX - digit) / 10)
{
atoi_error = "number too large/small";
return INT_MIN;
}
result = result * 10 + digit;
i++;
} // while
// Incorporate the sign and we're done.
return result * sign;
} // atoi_10
/**
* Base 8 atoi.
* Pre: a[0] = '0'
*/
static int
atoi_8 (char *a)
{
int i = 1; // Position in the string
int result = 0; // The computed result
int digit; // The value of the current digit
// Process remaining digits
while (a[i] != '\0')
{
if (! is_digit_8 (a[i]))
{
atoi_error = "invalid digit in base-8 number";
return INT_MIN;
}
digit = digit_value_16 (a[i]);
if (result > (INT_MAX - digit) / 8)
{
atoi_error = "number too large/small";
return INT_MIN;
}
result = result * 8 + digit;
i++;
} // while
// We're done
return result;
} // atoi_8
/**
* Base 16 atoi.
* Pre: a[0] = '0';
* a[1] = 'x' or a[1] = 'X'
*/
static int
atoi_16 (char *a)
{
int i = 2; // Position in the string
int result = 0; // The computed result
int digit; // The value of the current digit
// Special case: No digits
if (a[i] == '\0')
{
atoi_error = "missing digits in base-16 number";
return INT_MIN;
}
// Process remaining digits
while (a[i] != '\0')
{
if (! is_digit_16 (a[i]))
{
atoi_error = "invalid digit in base-16 number";
return INT_MIN;
}
digit = digit_value_16 (a[i]);
if (result > (INT_MAX - digit) / 16)
{
atoi_error = "number too large/small";
return INT_MIN;
}
result = result * 16 + digit;
i++;
} // while
// We're done
return result;
} // atoi_16
// +--------------------+---------------------------------------------
// | Exported Functions |
// +--------------------+
int
atoi (char *a)
{
// Special case: Empty string
if (a[0] == '\0')
{
atoi_error = "empty string";
return INT_MIN;
}
// Special cases: Begins with 0
if (a[0] == '0')
{
// If it's just "0" ...
if (a[1] == '\0')
return 0;
// If it's hex
else if ((a[1] == 'x') || (a[1] == 'X'))
return atoi_16 (a);
// Otherwise, it should be octal
else
return atoi_8 (a);
} // begins with 0
// Normal case
return atoi_10 (a);
} // atoi
A few of you discovered strtol. Unfortunately,
strtol does not report errors when errors are appropriate.
There were a number of issues with failure to report all errors. I expected you to handle:
Sunday, 3 April 2011 [Samuel A. Rebelsky]
Monday, 4 April 2011 [Samuel A. Rebelsky]
http://www.cs.grinnell.edu/~rebelsky/Courses/CSC161/2011S/Exams/notes02.html.
[Skip to Body]
Primary:
[Front Door]
[Schedule]
[Piazzza]
-
[Academic Honesty]
[Instructions]
Current:
[Current Outline]
[Current EBoard]
-
[Current Assignment]
[Current Lab]
[Current Reading]
Groupings:
[Assignments]
[Documents]
[EBoards]
[Examples]
[Exams]
[Handouts]
[Labs]
[Outlines]
[Readings]
[Reference]
Related Courses:
[CSC195 2003S (Rebelsky)]
[CSC161 2009F (Coahran)]
[CSC161 2010S (Walker)]
[CSC161 2010F (Rebelsky)]
Misc:
[SamR]
[ISO]
[GNU Coding Standards]
Disclaimer:
I usually create these pages on the fly
, which means that I rarely
proofread them and they may contain bad grammar and incorrect details.
It also means that I tend to update them regularly (see the history for
more details). Feel free to contact me with any suggestions for changes.
This document was generated by
Siteweaver on Mon Apr 4 10:46:44 2011.
The source to the document was last modified on Mon Apr 4 10:46:43 2011.
This document may be found at http://www.cs.grinnell.edu/~rebelsky/Courses/CSC161/2011S/Exams/notes.02.html.