Sonoma State University | ||
Algorithm Analysis
|
||
Instructor: Henry M. Walker
Lecturer, Sonoma State University |
Although CS 415 has been well developed for several years, last year the CS faculty made a significant, long-term curricular change regarding SSU's Upper Division GE Area B Requirement.
Historically, CS Majors could satisfy this requirement by taking CS 454, Theory of Computation, and CS 454 will continue in this role for the next several semesters.
At some time in the future (but not Spring 2025), CS 415, Algorithm Analysis, will allow students to satisfy SSU's Upper Division GE Area B Requirement.
During an anticipated time of transition:
For future semesters, students should check with the CS faculty regarding which course(s) (CS 415 and/or CS 454) will satisfy SSU's Upper Division GE Area B Requirement.
This assignment explores some practical consequences of the representation of data in program processing.
Recall from your prior work in C/C++ that constants variables INT_MIN and INT_MAX in C/C++ contain the smallest and largest int values available in C/C++s with the current hardware and compiler.
Finding Integer Averages: Given two
integers, i
and j
, an expression is
desired to compute their average (as an integer).
Notes, since this problem involves integers:
i
and j
, are the same, their average (of course)
should be i
(or j
). Thus, the average
of 5 and 5 should be 5, and the average of 6 and 6 should be 6.
Consider Tables 1 and 2 below, in which the first column
identifies five expressions that might be used to compute the
average of i
and j
.
i
and j
are non-negative integers. That is, for
each empty cell in the table, indicate
i
, j
values, but not others,
given the conditions indicated in the column header.
i
and j
can be any integers (positive, negative, or
zero). In completing Table 2:
Table 1: i , j can be any non-negative integers.
| overflow possible | ignoring overflow, expression gives correct answer | |||||
---|---|---|---|---|---|---|---|
expression | OK for all but a few i , j
| OK unless i=INT_MAX
| OK unless j=INT_MAX
| i even, j even
| i odd, j even
| i even, j odd
| i odd, j odd
|
avg1 = (i + j) / 2;
| |||||||
avg2 = i/2 + j/2;
| |||||||
avg3 = (i+1)/2 + j/2;
| |||||||
avg4 = (i+1)/2 + (j+1)/2;
| |||||||
avg5 = i + (j-i)/2;
|
Table 2: i , j can be any integers
(positive, negative, or zero)
| overflow possible | ignoring overflow, expression gives correct answer | |||||
---|---|---|---|---|---|---|---|
expression | OK for all but a few i , j
| OK unless i=INT_MAX ori=-INT_MAX
| OK unless j=INT_MAX orj=-INT_MAX
| i even, j even
| i odd, j even
| i even, j odd
| i odd, j odd
|
avg1 = (i + j) / 2;
| |||||||
avg2 = i/2 + j/2;
| |||||||
avg3 = (i+1)/2 + j/2;
| |||||||
avg4 = (i+1)/2 + (j+1)/2;
| |||||||
avg5 = i + (j-i)/2;
|
Consider the program integer-average.c.
The international standard for 64-bit floating-point numbers (often the basis for a double in C/C++) uses a binary version of scientific notation, with a sign, an exponent (a power of 2 in binary) and a mantissa (also as a binary number). With the internation standard for 64-bit floating point numbers, the bits are allocated as follows:
See Binary Representation of Floating-point Numbers for details.
In practice, this international binary standard does not store the leading mantissa bit (because in scientific notation for binary numbers, the leading bit is always 1 (the number 0 is treated in a different way). Thus, since the 64-bit standard explicitly stores 52 bits for the mantissa, this format actually can provide 53 bits of accuracy for stored numbers.
In binary, the decimal number 1023 can be represented with 10 bits. Thus, the decimal number 1,000 can be stored in about 10 binary bits, and 3 decimal-digit numbers require 10 binary bits. Using this perspective, the decimal number 1,000,000 can be stored with about 20 bits, and about 6 decimal-digit numbers require about 20 bits. Continuing this insight, about 15 decimal-digit numbers require about 50 bits.
Also, the decimal digit 8 requires about 3 binary bits, so 3 binary bits allows storage for roughly another decimal digit).
Putting these observations together, we might expect that the 53-bits utilized in the 64-bit international standard can store about 16 decimal digits of accuracy.
To gain first-hand experience with the storage of double numbers in C/C++, Problem 3 considers the storage of the following numbers.
"0.1234567890123456789012345678901234567890" ; // digits for easy counting "0.2424242424242424242424242424242424242424" ; // all digits < 5 "0.6868686868686868686868686868686868686868" ; // all digits > 5
Although modern C/C++ compilers often allocate 64 bits for the double data type, the C/C++ standard does not specify this size for all computers and compilers; rather, the number of bits for doubles is machine dependent—the number of bits, and the corresponding number of decimal digits stored may vary from one computing environment to another.
To investigate the storage of double numbers, the
program double-storage.c prints the
double
to several decimal places of accuracy.
Download and compile this program.
Run the program, based on the number
where the digit pattern can help count individual decimal places.
Repeat part a, after modifying the program to process the number
where all digits are < 5, so no rounding would be appropriate.
Repeat part a, after modifying the program to process the number
where all digits are > 5, so rounding up would always be appropriate;
Over the years, many approaches have been developed to compute the value of the number π. Many of these approaches are based on an infinite series, one of which is
Details behind this formula may be found in a Wikipedia article on Leibniz formula for π and a stockexchange.com article on Series that converge to π quickly.
Although this is an infinite sum, calculus (and algebra) indicates that successively better approximations to π may be obtained by including more and more terms of the series. Also, it is worth noting that computationally each term is smaller than the previous.
Program pi-approx.c
Read, analyze, download, compile, and run program pi-approx.c
T[i] = 2.0 * i * T[i-1] / (2.0 * (2.0*i+1.0));Based on this computation, explain algebraically why each computed term is progressively smaller than the previous.
Our discussions of the representation of real numbers (doubles and floats) have identified at least three factors that can cause errors in processing—particularly if the errors can accumulate as processing continues.
Be sure to take these potential troubles into account in answering Steps 5 and 6.
Given that start < end
, suppose a loop is to
begin at start
and finish at (or near)
end
in exactly n+1
iterations. Within
this loop, suppose the control variable will increase by a
computed value increment = (end-start)/n
with each iteration.
Two loop structures are proposed:
// approach 1 increment = (end - start)/n; for (i = 0; i <= n; i++){ value = start + i * increment; /* processing for value */ }
// approach 2 value = start; increment = (end - start)/n; while (value <= end) { /* processing for value */ value += increment; }
Although the first approach requires somewhat more arithmetic within the loop than the second, it likely will provide better accuracy. Identify two distinct reasons why the first approach should be preferred over the second.
Suppose y = f(x)
is a function that decreases
significantly from x=a
to x=b
, on the
interval
[a, b]
, with a < b
.
Throughout this interval, assume f(x)>0
, and
assume the Trapezoidal Rule were to be used to approximate the
area under y = f(x)
on the interval
[a, b]
.
Assuming accuracy is the highest priority for this
computation, should the main loop begin at a
and
go toward b
or begin at b
and go
toward a
, or is either order fine? Explain.
Again, assuming accuracy of the answer is the highest
priority, write a reasonably efficient code that implements
the Trapezoidal Rule for this function on this interval.
(To be reasonably efficient, f(x) should be computed only
once for each value of x, and division by 2 should be done
as little as possible, as discussed in class.)
Be sure
to include your code within a program, and run several tests
of the program.
For this step, submit both the program and the output from
several test runs.
(Of course, your program must conform to the course's C/C++
Style Guide.)
Explain how and why your approach to this problem (with
f(x)
decreasing significantly
from x=a
to x=b
) should be different
from the code when
f(x)
increases over this interval.
created 31 March 2022 expanded 24 July 2022 expanded 3 January 2023 modest editing Summer 2023 revised 20 November 2024 |
|
For more information, please contact Henry M. Walker at walker@cs.grinnell.edu. |
Copyright © 2011-2025
by Henry M. Walker.
|