Notes on Computer Project #4 ---------------------------- Comments about the assignment and responses to frequently asked questions will be added to this file as necessary. ***** comments added on 01/31/12 ***** 1) As stated on the assignment handout, you are required to create a makefile which controls the translation of your program, and the name of the executable file produced by the makefile must be "proj04". 2) The functions which you will develop are declared in the following file: /user/cse232/Projects/project04.library.h Since that file may not be modified, there is no reason to copy it into your account. Instead, use its absolute pathname in the appropriate preprocessor directive. For example: #include "/user/cse232/Projects/project04.library.h" Any application program which uses the library should contain the appropriate preprocessor directive. 3) Please note the following from the project handout: The driver module will consist of function "main" and any auxiliary functions that directly support "main". The driver module will demonstrate that each function in the library module is implemented correctly. All output displayed by the driver module will be appropriately formatted and labeled. Your driver module will not be interactive. If your driver module accepts any input, you will supply that input in a text file named "proj04.tests". An interactive program is one in which the user enters inputs in response to prompts. Since that approach is prohibited for this assignment, your driver module will NOT contain code segments such as the following: cout << "Enter an angle in radians:"; cin >> AngleRadians; Instead of prompting the user to enter test cases, there are two approaches you may use: a) Embed the test cases in the driver module as constants. b) Input the test cases from a text file (without prompting the user). The first approach (embedding the test cases in the driver module) is the preferred approach for this project since it will facilitate incremental development of the driver module and the library module. For example, if you were going to test "sin" (from the math library), you might use a series of statements such as: cout << sin( 0.0 ) << endl; cout << sin( 0.5 ) << endl; cout << sin( 1.0 ) << endl; Obviously, that code segment is too crude -- the test cases aren't very good, and the output isn't labeled or formatted. However, it illustrates the general approach for a driver module which doesn't accept any input. The second approach (inputting the test cases from a text file) can be useful in some circumstances, but might be problematic for this project. If you decide to use that approach, you would then use input redirection when you executed your program: a.out < proj04.tests Regardless of which of the two methods you use to develop your driver module, note that your test cases will be available when your solution is graded since they will either be embedded in the driver module or present in "proj04.tests". 4) Please note the following from the project handout: The functions in the library module will not use the following mathematical functions (declared in "cmath"): "fabs", "fmod", "pow", "sin" and "cos". If you were allowed to call those standard functions from your library module, then the definitions of your functions would be trivial. For example: double cosine( double x ) { return cos( x ); } Function definitions such as the one above obviously undermine the whole point of the project and are prohibited. In your driver module, you may call any functions that you wish. For example, you may wish to call "cos" (the standard math library function) and compare the value it returns against the value returned from your function "cosine". You may wish to use some of the symbolic constants declared in the standard math library in your driver module and/or your library module. For example: M_PI, INFINITY, NAN Those symbolic constants are the appropriate values (bit patterns) for Pi, Infinity, and Not-A-Number on the CSE Linux machines. 5) When in doubt, make your functions perform the same way as the functions in the standard math library. Here's the mapping: absolute -- fabs modulo -- fmod factorial -- no equivalent function power -- pow sine -- sin cosine -- cos Of course, your functions should perform as specified on the assignment handout for error conditions. Please note that the "fmod" function (and probably your "modulo" function) may produce unusual results for some values. For example: cout << fmod( 2.8, 1.4 ) << endl; // outputs 0 cout << fmod( 4.2, 1.4 ) << endl; // outputs 4.44089e-16 The second output is clearly wrong. The problem arises for some values that can't be represented exactly -- round-off errors lead to incorrect results. You can ignore the problem (since "fmod" is your benchmark), or you can try to beat that benchmark using fixed point computations. For example, you could convert the operands to fixed point values (by multiplying by a large integer value, such as 1000000), get the remainder, and convert back to a floating point value. 6) Please note that function "factorial" returns a value of type "double", even though its argument is a value of type "unsigned int". Recall one benefit of using integer computations: the Integer Unit usually performs operations more quickly than the Floating Point Unit. One of the drawbacks to using integer computations is that the range of integer numbers which can be represented is much smaller than the range of real numbers which can be represented. The relevant constants (found in "limits.h") are listed below: UINT_MAX 4294967295U /* maximum value of an "unsigned int */ DBL_MAX 1.7976931348623157E+308 /* maximum value of a "double" */ Because of the limitation on the size of a value of type "unsigned int", the largest factorial value which can be computed using the Integer Unit is 12!, which may not be sufficient for some calculations. Thus, you should convert the argument from type "unsigned int" to type "double" before performing any calculations. If you wish to display a value of type "double" which contains an integer value and you don't want to see a decimal point or any fractional digits, you can use the facilities of the "Stream" library to control the formatting. 7) Please note that the number of terms used in the power series to get a good approximation of the sine or cosine of X will increase as X gets larger. For example, it might take 7 terms in the power series to compute the sine of 1.0, while it might take 9 terms to compute the sine of 2.0 (and so on). Recall that the sine and cosine functions are periodic functions, with a period of 2*PI. Thus, you may wish to map the value of X (the function argument) into a specific range (such as 0 to 2*PI or -PI to +PI). This will reduce the number of terms which must be computed to get a good approximation. 8) Floating point literal constants are expressed using a mix of decimal digits and other characters. For example, the value forty three and one half can be expressed as 43.5, 4.35e+1, 435.0e-1 (and so on). The floating point value Infinity is expressed as INFINITY and is the result of a floating point calculation which overflows. The floating point value Not-A-Number is expressed as NAN and is the result of an illegal floating point calculation (such as division by zero). Please note that the floating point values Infinity and Not-A-Number are valid values of type "double", so the functions in your library module should be prepared to receive those values as arguments. Also, please note that error conditions are indicated by returning the floating point values Infinity or Not-A-Number. I have also created a program to help you explore this area: ~cse232/Projects/project04.demo.cpp Translation instructions are included in the comments. Note that an equality comparison between NaN and another value never yields "true", even if the other value is NaN (that's the way the IEEE standard is defined). You can use the "finite" function, as in the sample program, to differentiate between valid values and invalid values (Infinity and NaN). 9) Please note that it is simply not possible for a C++ function to receive an argument of the wrong type -- the compiler will not permit it. For example, consider a function declared as follows: void whatever( double ); Then, consider the following invocations: whatever( 5.7 ); whatever( 200 ); // Argument of type "int" implicitly converted whatever( "check" ); // Argument of type "string" flagged by compiler If the type of the actual argument does not match the type of the declared parameter, the compiler will attempt to convert it (using the rules built into C++ about "promotion"). If no conversion is possible, then the compiler reports a fatal syntax error. 10) Please note that function "modulo" operates on floating point values. Function "modulo" will return the remainder after the divisor has been removed from the dividend an integer number of times. For example, 5.5 modulo 2.6 yields 0.3 (since 5.5 - 2*2.6 = 0.3). As noted above, your functions should behave in the same manner as the equivalent function in the standard math library. More information about the standard functions is available via the "man" utility. For example: man fmod As noted in the "man" entry for "fmod", the calculated value should have the same sign as the dividend and a magnitude less than the divisor. Consider the following code segment: x = +6.75; y = +2.0; z = fmod( x, y ); cout << "x: " << x << " y: " << y << " z: " << z << endl; x = -6.75; y = +2.0; z = fmod( x, y ); cout << "x: " << x << " y: " << y << " z: " << z << endl; x = +6.75; y = -2.0; z = fmod( x, y ); cout << "x: " << x << " y: " << y << " z: " << z << endl; x = -6.75; y = -2.0; z = fmod( x, y ); cout << "x: " << x << " y: " << y << " z: " << z << endl; When executed, that code segment produces: x: +6.75 y: +2.00 z: +0.75 x: -6.75 y: +2.00 z: -0.75 x: +6.75 y: -2.00 z: +0.75 x: -6.75 y: -2.00 z: -0.75 Ideally, your implementation of "modulo" will behave in the same way. --M. McCullen