Homework #3#
Completing this assignment
For the each of the problems below, write a separate C++ program
named in the problem problemN.cpp, where N is the
problem number.
Important
Make sure your that g++ can compile your code. For some of
the problems here, you will need to use -std=c++20.
Upload your C++ source files (not the executables produced by g++) to Brightspace.
Important
All work must be your own.
You may not use generative AI / large-language models for any part of this assignment.
Roundoff I: To get a sense of roundoff, write a program to compute
0.3 / 0.1 - 3withdoubledatatypes. Output with enough precision to see if you get the expected result.solution
#include <iostream> int main() { double ans = 0.3 / 0.1 - 3; std::cout << ans << std::endl; }
When run, this gives
-4.44089e-16and not zero, demonstrating that there is roundoff error in this expression.Roundoff II: Now let’s see how to mitigate roundoff error.
Consider the function:
\[f(x) = \frac{1}{\sqrt{x^3 + 1} - 1}\]defined for \(x > 0\). When \(x\) is small, roundoff error from the subtraction will dominate the answer. We want to see if we can get a better result.
By by multiplying and dividing \(f(x)\) by \(\sqrt{x^3 + 1} + 1\), you get an analytically equivalent expression without any subtractions. Call this new expression \(g(x)\).
your task: Write a program to evaluate \(f(x)\) and \(g(x)\) for the values of \(x\):
1.e-4,1.e-5, and1.e-6.Note
Don’t just multiply \(f(x)\) by \(\sqrt{x^3 + 1} + 1\) in your code—you need to do this analytically and simplify to define \(g(x)\).
What do you observe?
solution
#include <iostream> #include <cmath> int main() { double x{1.e-4}; double f = 1.0 / (std::sqrt(std::pow(x, 3.0) + 1.0) - 1.0); // multiplying and dividing f by (sqrt(x^3 + 1) + 1) and // simplifying double g = (std::sqrt(std::pow(x, 3.0) + 1.0) + 1.0) / std::pow(x, 3.0); std::cout << x << " " << f << " " << g << std::endl; x = 1.e-5; f = 1.0 / (std::sqrt(std::pow(x, 3.0) + 1.0) - 1.0); g = (std::sqrt(std::pow(x, 3.0) + 1.0) + 1.0) / std::pow(x, 3.0); std::cout << x << " " << f << " " << g << std::endl; x = 1.e-6; f = 1.0 / (std::sqrt(std::pow(x, 3.0) + 1.0) - 1.0); g = (std::sqrt(std::pow(x, 3.0) + 1.0) + 1.0) / std::pow(x, 3.0); std::cout << x << " " << f << " " << g << std::endl; }
when run, we get:
0.0001 1.99982e+12 2e+12 1e-05 2.2518e+15 2e+15 1e-06 inf 2e+18
Notice that for the smallest value of
x, we get aninffrom the original expression. This is because we are doingstd::pow(1.e-6, 3.0) + 1.0which is the same as1.e-18 + 1.0, which because of roundfoff is1.0, so the denominator cancels!.Trig functions: Consider \(\sin(x)\). The Taylor expansion of \(\sin(x)\) is:
\[\sin(x) = x - \frac{x^3}{3!} + \frac{x^5}{5!} + \ldots\]Let’s consider the small-angle approximation:
\[\sin(x) \approx x\]where \(x\) is measured in radians.
We want to see how good this approximation is for different values of \(x\)
Your task: write a program that computes \(\sin(x)\) and the error, \(\epsilon \equiv|\sin(x) - x|\), for \(x = 5^\circ, 10^\circ, 20^\circ, \mbox{and}\ 40^\circ\).
Note
You’ll need to convert these angles to radians in your code using the value of \(\pi\) that C++ provides.
Have your program output, for each angle, a line with the angle in degrees, the angle in radians, the sine of the angle, and the error in the small-angle approximation (4 columns).
solution
#include <iostream> #include <cmath> #include <numbers> int main() { double pi = std::numbers::pi; double angle_degrees = 5.0; double angle_radians = angle_degrees * pi / 180.0; double sine = std::sin(angle_radians); std::cout << angle_degrees << " " << angle_radians << " " << sine << " " << std::abs(sine - angle_radians) << std::endl; angle_degrees = 10.0; angle_radians = angle_degrees * pi / 180.0; sine = std::sin(angle_radians); std::cout << angle_degrees << " " << angle_radians << " " << sine << " " << std::abs(sine - angle_radians) << std::endl; angle_degrees = 20.0; angle_radians = angle_degrees * pi / 180.0; sine = std::sin(angle_radians); std::cout << angle_degrees << " " << angle_radians << " " << sine << " " << std::abs(sine - angle_radians) << std::endl; angle_degrees = 40.0; angle_radians = angle_degrees * pi / 180.0; sine = std::sin(angle_radians); std::cout << angle_degrees << " " << angle_radians << " " << sine << " " << std::abs(sine - angle_radians) << std::endl; }
Tip
We will be able to make this code a lot simpler soon, once we learn about loops.
when run, we get:
5 0.0872665 0.0871557 0.00011072 10 0.174533 0.173648 0.000884748 20 0.349066 0.34202 0.00704571 40 0.698132 0.642788 0.0553441
We see that the error is less than 1% even for 20 degrees.
Overflow: A
short intuses only 2 bytes of memory instead of 4 bytes for a normalint. This means that there are only \(2^{16}\) of \(32,768\) possible values. We want to see overflow in action.Your task: write a program that does the following:
Initialize a
short intto the largest possible value, following the ideas we saw in class in the Reporting limits discussion.Use the postfix operator (see Prefix and postfix operators) to increment the value of your variable.
Output the updated value to the screen.
solution
#include <iostream> #include <limits> int main() { short int x = std::numeric_limits<short int>::max(); x++; std::cout << "x is now: " << x << std::endl; }
when run, we get:
x is now: -32768
Because a
short intis signed, when we overflow by exceeding the maximum integer it can represent we can the smallest (most negative) integer.