The requirements are fairly straightforward, although up to some
interpretation. The problem statement in the book says nothing about how
numbers are input or the results are verified.
With that in mind, I'll keep on my current trend of reading
numbers from standard input until an EOF is encountered.
That should keep things fairly simple. After each line of input, the program
will output to standard output both the number and the result.
Of course, "infinity" is a concept abhorrent to computers. There
are some clever ways of getting around the "infinity" concept for symmetrical
distributions like the normal distribution: since the integral over the
entire function is 1, integrating from -infinity to 2.5 is the same as adding
the integral from 0 to 2.5 to 0.5. But that's very specific to distributions
like the normal distribution, etc, which are in fact symmetrical about 0 and
integrate to 1, so I don't want to build that kind of functionality into the
integrator proper; it'll be handled by an indirection object of some sort.
Output will
show the number input, followed by the normal integral from -infinity to the
input, thus:
Ooog. Once again I'm confused by the PROBE method.
After a quick conceptual design, I decided on four new objects: a
single_variable_equation (very small math object), a
normal_distribution_base_equation medium math object),
a normal_input_parser (medium i/o object), and a
simpson_integrator. Combined with guesses of numbers of
methods, this came to about 72 new LOC. Running all this through the
PROBE process resulted in an estimate of 160 new/changed LOC (after
program 4a ballooned from my guess of 49 to over 129 LOC, the
process has ballooned expectations a bit).
It's worth noting that my linear regression parameters as taken from
my original estimates compared to actual results were so unacceptable according to the
PROBE script that I had to just take averages rather than
actually using linear regression. I don't feel comfortable with that, but I agree
that programs 2a and 3a have very much skewed things.
Adding to the confusion is the system of naming things and trying to
figure out which variables are fed into which equations. PROBE
comes up with estimated new/changed LOC, but I have to come up
with a new/changed LOC estimate to feed to PROBE.
Which of these gets fed into the linear regression calculations for the next
estimation session-- mine, or PROBE's? I say mine. Why? Because
PROBE is trying to form an actual estimate based on my guesses; if
I feed the results back into itself, PROBE will try and guess based
of my guesses and its guesses combined, treating the result as one of my guesses.
This is fairly unacceptable. For the future, then, I'll be placing my guess of
new and changed LOC as the x-value in future PROBE estimations,
with the actual new/changed LOC as the y-value.
I have similar difficulty with choosing parameters for the
time estimation; my results have been odd enough so far that I was forced to
choose average values again. Here, I am asked to relate estimated new/changed
LOC to actual time; in the future, I'll be relating
PROBE's size estimates to actual time spent. Note the difference;
for size estimations I'll relate my guesses to actual size;
for time estimations I'll relate PROBE's
estimates to actual time. That ought to sort things out a bit.
With PROBE's estimate of 160 new/changed LOC,
my best guess at time (1.168 minutes/LOC) means about
186 minutes for program 5A.
To encapsulate the concept of a single-variable function,
I'll create a very simple base class, single_variable_function, with
one feature : at( x: double ). Calling at with a value will
return the value of the equation, evaluated at the given value.
This will be used as a base for both the normal_distribution_base
class (the function which is integrated to get our desired value) and the
normal_distribution_from_negative_infinity class
(long name, but descriptive; its at function returns
the value of the normal integral evaluated from negative infinity to the
given value, handling the "infinity workaround" described in the planning
section).
The bulk of that work will be done by the simpson_integrator
class, which encapsulates the Simpson integration logic and can integrate
any single_variable_function we throw at it.
To handle the i/o, our brave simple_input_parser
gets reused once again, to form the normal_distribution_input_parser,
which takes a series of doubles from standard input and prints the resulting
values to standard output.
No surprises here. I did make a few very minor changes from
the original design, most having to do with the abstraction of the
normal_input_parser into a more generic
single_function_input_parser, in case I have
to reuse this with different functions.
This handy class remains the same as it ever has.
/*
*/
#include <fstream>
#include <iostream>
#include "string.h"
#ifndef SINGLE_FUNCTION_INPUT_PARSER_H
#include "single_function_input_parser.h"
#endif
#ifndef NORMAL_DISTRIBUTION_INTEGRAL_H
#include "normal_distribution_integral.h"
#endif
istream *
input_stream_from_args( int arg_count, const char** arg_vector )
{
istream* Result = NULL;
if ( arg_count == 1 )
{
Result = &cin;
}
else
{
const char* help_text = "PSP exercise 5A: Integrate the normal distribution from\nnegative infinity to the values given from standard input\nUsage: \n\tpsp_5a\n\n";
cout << help_text;
}
return Result;
}
int main( int arg_count, const char** arg_vector )
{
//get the input stream, or print the help text as appropriate
istream* input_stream = input_stream_from_args( arg_count, arg_vector );
if ( input_stream != NULL )
{
single_function_input_parser parser;
parser.set_input_stream( input_stream );
normal_distribution_integral f;
parser.set_function( &f );
parser.parse_until_eof();
}
}
/*
*/ |
/*
*/
#ifndef SINGLE_VARIABLE_FUNCTION_H
#define SINGLE_VARIABLE_FUNCTION_H
class single_variable_function
{
public:
//the value of the function, evaluated at x
virtual double at( double x ) const = 0;
};
#endif
/*
*/ |
/*
*/
#ifndef NORMAL_FUNCTION_BASE_H
#define NORMAL_FUNCTION_BASE_H
#ifndef SINGLE_VARIABLE_FUNCTION_H
#include "single_variable_function.h"
#endif
class normal_function_base : public single_variable_function
{
public:
virtual double at( double x ) const;
};
#endif
/*
*/ |
/*
*/
#include "normal_function_base.h"
#include <math.h>
double
normal_function_base::at( double x ) const
{
const double base_value = 1 / ( sqrt( M_PI * 2 ) );
return base_value * exp( -(x * x ) / 2 );
}
/*
*/ |
#ifndef NORMAL_DISTRIBUTION_INTEGRAL_H
#define NORMAL_DISTRIBUTION_INTEGRAL_H
#ifndef NORMAL_FUNCTION_BASE_H
#include "normal_function_base.h"
#endif
#ifndef SIMPSON_INTEGRATOR_H
#include "simpson_integrator.h"
#endif
class normal_distribution_integral : public single_variable_function
{
public:
virtual double at( double x ) const;
protected:
simpson_integrator homer;
normal_function_base base;
};
#endif |
#include "normal_distribution_integral.h"
#ifndef CONTRACT_H
#include "contract.h"
#endif
double
normal_distribution_integral::at( double x ) const
{
double Result = 0;
if ( x < 0 )
{
Result = 0.5 - homer.integral( base, 0, 0-x );
}
else if ( x > 0 )
{
Result = 0.5 + homer.integral( base, 0, x );
}
else if ( x == 0 )
{
Result = 0.5;
}
else
{
CHECK( false );
}
return Result;
} |
/*
*/
#ifndef SIMPSON_INTEGRATOR_H
#define SIMPSON_INTEGRATOR_H
#ifndef SINGLE_VARIABLE_FUNCTION_H
#include "single_variable_function.h"
#endif
class simpson_integrator
{
public:
//sets the acceptable error in the computation
void set_acceptable_error( double new_error );
//sets the starting number of segments
void set_starting_segment_count( int new_starting_segment_count );
//returns the simpson integral of the given single variable function between the two limits
double integral( const single_variable_function& f, double lower_limit, double upper_limit ) const;
//constructor
simpson_integrator::simpson_integrator( void );
protected:
double acceptable_error;
int starting_segment_count;
double single_pass( const single_variable_function& f, double lower_limit, double upper_limit, int segment_count ) const;
bool is_odd( int x ) const;
};
#endif
/*
*/
|
/*
*/
#include "simpson_integrator.h"
#ifndef CONTRACT_H
#include "contract.h"
#endif
void
simpson_integrator::set_acceptable_error( double new_error )
{
REQUIRE( new_error > 0 );
acceptable_error = new_error;
}
void
simpson_integrator::set_starting_segment_count( int new_starting_segment_count )
{
REQUIRE( new_starting_segment_count > 0 );
REQUIRE( !is_odd( new_starting_segment_count ) );
starting_segment_count = new_starting_segment_count;
}
simpson_integrator::simpson_integrator( void ) :
acceptable_error( 0.00001 ),
starting_segment_count( 20 )
{
}
double
simpson_integrator::integral( const single_variable_function& f,
double lower_limit, double upper_limit ) const
{
double Result = 0;
double previous_result = 0;
double last_error = 0;
int segment_count = starting_segment_count;
bool result_has_been_calculated = false;
while ( !result_has_been_calculated
|| ( last_error > acceptable_error ) )
{
previous_result = Result;
Result = single_pass( f, lower_limit, upper_limit, segment_count );
last_error = fabs( Result - previous_result );
//cout << "Segments: " << segment_count << "; error: " << last_error << "\n";
segment_count *= 2;
result_has_been_calculated = true;
}
return Result;
}
double
simpson_integrator::single_pass( const single_variable_function& f,
double lower_limit, double upper_limit,
int segment_count ) const
{
double Result = 0;
const double segment_width = ( upper_limit - lower_limit ) / static_cast<double>(segment_count);
const double third_width = segment_width / static_cast< double >(3);
Result = f.at( lower_limit ) * third_width;
//cout << "From " << lower_limit << " to " << upper_limit << "\n";
//cout << "Width " << segment_width << "\n";
//cout << "Term 0: " << Result << "\n";
for ( int i = 1; i < segment_count; ++i )
{
const double xi = lower_limit + static_cast< double >( i ) * segment_width;
const double segment_value = f.at( xi ) * third_width;
//cout << "Xi: " << xi << " ";
//cout << "F: " << f.at( lower_limit + static_cast<double>(i) * segment_width ) << " ";
if ( is_odd(i) )
{
Result += 4 * segment_value;
//cout << "Term " << i << ": " << 4 * segment_value << "\n";
}
else
{
Result += 2 * segment_value;
//cout << "Term " << i << ": " << 2 * segment_value << "\n";
}
}
//cout << "f( upper ): " << f.at( upper_limit ) << "\n";
Result += f.at( upper_limit ) * third_width;
CHECK( fabs( upper_limit - ( lower_limit + segment_count * segment_width ) ) < acceptable_error );
return Result;
}
bool
simpson_integrator::is_odd( int x ) const
{
return ( x & 1 );
}
/*
*/ |
/*
*/
#ifndef SINGLE_FUNCTION_INPUT_PARSER_H
#define SINGLE_FUNCTION_INPUT_PARSER_H
#ifndef SIMPLE_INPUT_PARSER_H
#include "simple_input_parser.h"
#endif
#ifndef SINGLE_VARIABLE_FUNCTION_H
#include "single_variable_function.h"
#endif
class single_function_input_parser : public simple_input_parser
{
public:
virtual void parse_last_line( void );
void set_function( single_variable_function* const new_function );
single_function_input_parser( void );
protected:
single_variable_function* f;
};
#endif
/*
*/ |
/*
*/
#include "single_function_input_parser.h"
#ifndef CONTRACT_H
#include "contract.h"
#endif
void
single_function_input_parser::parse_last_line( void )
{
REQUIRE( f != NULL );
char* conversion_end = NULL;
double new_x = strtod( last_line().c_str(), &conversion_end );
if ( conversion_end == last_line().data() )
{
cerr << "Not a double : " << last_line() << "\n";
}
else
{
cout << new_x << ": " << f->at( new_x ) << "\n";
}
}
void
single_function_input_parser::set_function( single_variable_function* const new_function )
{
f = new_function;
}
single_function_input_parser::single_function_input_parser( void ):
f( NULL )
{
}
/*
*/ |
class MAIN
creation
make
feature { ANY }
make is
local
parser : SINGLE_FUNCTION_INPUT_PARSER
f : NORMAL_DISTRIBUTION_INTEGRAL
do
!!parser
!!f.make
parser.set_f( f )
parser.set_input( io )
parser.parse_until_eof
end
end-- MAIN |
deferred class SINGLE_VARIABLE_FUNCTION
--represents a calculable function
feature { ANY }
at( x: DOUBLE ) : DOUBLE is
--value of the function evaluated at x
deferred
end
end |
class NORMAL_FUNCTION_BASE
--base function for evaluating the normal distribution integral
inherit
SINGLE_VARIABLE_FUNCTION
redefine
at
end
feature {ANY}
base_value : DOUBLE is
once
Result := 1 / ( 2.0 * Pi ).sqrt
end
at( x :DOUBLE ) : DOUBLE is
--value of the function at x
do
Result := base_value * ( - (x*x)/2 ).exp
end
end
|
class NORMAL_DISTRIBUTION_INTEGRAL
--calculates the integral of the normal distribution
inherit
SINGLE_VARIABLE_FUNCTION
redefine
at
end
creation
make
feature {ANY}
f : NORMAL_FUNCTION_BASE
homer : SIMPSON_INTEGRATOR
make is
do
!!f
!!homer.make
end
at( x : DOUBLE ) : DOUBLE is
--integral of the normal distribution from -infinity to x
do
if ( x < 0 ) then
Result := 0.5 - homer.integral( f, 0, -x )
elseif ( x > 0 ) then
Result := 0.5 + homer.integral( f, 0, x )
elseif ( x = 0 ) then
Result := 0.5
end
end
end
|
class SIMPSON_INTEGRATOR
--integrates a SINGLE_VARIABLE_FUNCTION over a range
creation
make
feature { ANY }
acceptable_error : DOUBLE
set_acceptable_error( new_error : DOUBLE ) is
require
acceptable_error > 0.0
do
acceptable_error := new_error
end
starting_segment_count : INTEGER
set_starting_segment_count( new_starting_segment_count : INTEGER ) is
require
new_starting_segment_count > 0
new_starting_segment_count.even
do
starting_segment_count := new_starting_segment_count;
end
make is
--set initial variables
do
acceptable_error := 0.0001
starting_segment_count := 20
end
integral( f : SINGLE_VARIABLE_FUNCTION; lower_limit, upper_limit : DOUBLE ) : DOUBLE is
--integral of f from lower_limit to upper_limit
local
previous_result : DOUBLE
last_error : DOUBLE
result_has_been_calculated : BOOLEAN
segment_count : INTEGER
do
from
previous_result := 0
last_error := 0
segment_count := starting_segment_count
result_has_been_calculated := false
until
result_has_been_calculated and ( last_error < acceptable_error )
loop
previous_result := Result
Result := single_pass( f, lower_limit, upper_limit, segment_count )
last_error := ( Result - previous_result ).abs
result_has_been_calculated := true
segment_count := segment_count * 2
end
end
single_pass ( f : SINGLE_VARIABLE_FUNCTION; lower_limit, upper_limit: DOUBLE; segment_count : INTEGER ): DOUBLE is
-- calculates a single pass of the algorithm
local
i : INTEGER
segment_width : DOUBLE
segment_value : DOUBLE
third_width : DOUBLE
xi : DOUBLE
do
segment_width := ( upper_limit - lower_limit ) / segment_count.to_double
third_width := segment_width / 3.0
check
( lower_limit + segment_count.to_double * segment_width - upper_limit ).abs < acceptable_error
end
Result := f.at( lower_limit ) * third_width;
from
i := 1
until
i = segment_count
loop
xi := lower_limit + i.to_double * segment_width;
segment_value := f.at( xi ) * third_width
if i.odd then
Result := Result + 4.0 * segment_value
else
Result := Result + 2.0 * segment_value
end
i := i + 1
end
Result := Result + f.at( upper_limit ) * third_width
end
end
|
class SINGLE_FUNCTION_INPUT_PARSER
--inputs numbers and calculates a given SINGLE_VARIABLE_FUNCTION
--at each
inherit
SIMPLE_INPUT_PARSER
redefine
parse_last_line
end
feature { ANY }
f : SINGLE_VARIABLE_FUNCTION
set_f( new_f : SINGLE_VARIABLE_FUNCTION ) is
do
f := new_f
end
parse_last_line is
--reads last line as a double and calculates f at that value
require
f /= void
local
new_x : DOUBLE
do
if ( last_line.is_double or last_line.is_integer ) then
new_x := double_from_string( last_line )
std_output.put_double( new_x )
std_output.put_string( ": " )
std_output.put_double( f.at( new_x ) )
std_output.put_new_line
else
std_error.put_string( "Not a double: " + last_line + "%N" )
end
end
double_from_string ( string : STRING ) : DOUBLE is
require
string.is_double or string.is_integer
do
if string.is_double then
Result := string.to_double
elseif string.is_integer then
Result := string.to_integer.to_double
end
end
end
|
No surprises here, except my continual problems: forgetting to update
loop variables in Eiffel, forgetting to instantiate variables in Eiffel, forgetting
to match up header definitions and implementations in C++
Although this revealed some problems, overall the design worked fine.
Results are below:
Table 5-3. Test Results Format -- Program 5A
Test Value -- X | Expected Results | Actual Results: C++ | Actual Results : Eiffel |
2.5 | 0.9938 | 0.99379 | 0.993790 |
0.2 | 0.5793 | 0.57926 | 0.579260 |
-1.1 | 0.1357 | 0.135666 | 0.135666 |
Table 5-4. Project Plan Summary
Student: | Victor B. Putz | Date: | 000106 |
Program: | Numeric integrator | Program# | 5A |
Instructor: | Wells | Language: | C++ |
Summary | Plan | Actual | To date |
Loc/Hour | 50 | 34 | 48 |
Planned time | 186 | | 186 |
Actual time | | 182 | 182 |
CPI (cost/performance index) | | | 1.02 |
%reused | 22 | 31 | 31 |
Program Size | Plan | Actual | To date |
Base | 25 | 25 | |
Deleted | 0 | 5 | |
Modified | 5 | 1 | |
Added | 155 | 103 | |
Reused | 52 | 55 | 142 |
Total New and Changed | 160 | 104 | 790 |
Total LOC | 232 | 178 | 1144 |
Total new/reused | 0 | 0 | 0 |
Time in Phase (min): | Plan | Actual | To Date | To Date% |
Planning | 26 | 67 | 183 | 18 |
Design | 19 | 24 | 103 | 10 |
Code | 50 | 42 | 268 | 27 |
Compile | 11 | 13 | 65 | 6 |
Test | 65 | 25 | 312 | 31 |
Postmortem | 15 | 11 | 77 | 8 |
Total | 186 | 182 | 1008 | 100 |
Defects Injected | | Actual | To Date | To Date % |
Plan | | 0 | 0 | 0 |
Design | | 4 | 25 | 25 |
Code | | 15 | 70 | 69 |
Compile | | 0 | 3 | 3 |
Test | | 0 | 3 | 3 |
Total development | | 19 | 101 | 100 |
Defects Removed | | Actual | To Date | To Date % |
Planning | | 0 | 0 | 0 |
Design | | 0 | 0 | 0 |
Code | | 3 | 22 | 21 |
Compile | | 14 | 49 | 49 |
Test | | 2 | 30 | 30 |
Total development | | 19 | 101 | 100 |
After Development | | 0 | 0 | |
Time in Phase (min) | Actual | To Date | To Date % |
Code | 31 | 146 | 46 |
Compile | 13 | 76 | 24 |
Test | 6 | 95 | 30 |
Total | 50 | 317 | 100 |
Defects Injected | Actual | To Date | To Date % |
Design | 0 | 4 | 6 |
Code | 13 | 58 | 92 |
Compile | 0 | 0 | 0 |
Test | 0 | 1 | 2 |
Total | 13 | 63 | 100 |
Defects Removed | Actual | To Date | To Date % |
Code | 0 | 1 | 2 |
Compile | 11 | 39 | 62 |
Test | 2 | 23 | 36 |
Total | 13 | 63 | 100 |
Table 5-5. Time Recording Log
Student: | 5A | Date: | 000108 |
| | Program: | Victor Putz |
Start | Stop | Interruption Time | Delta time | Phase | Comments |
000108 11:29:59 | 000108 12:58:05 | 21 | 67 | plan | |
000108 13:10:06 | 000108 13:34:09 | 0 | 24 | design | |
000108 13:54:41 | 000108 14:37:21 | 0 | 42 | code | |
000108 14:39:39 | 000108 14:53:06 | 0 | 13 | compile | |
000108 14:53:10 | 000108 15:18:55 | 0 | 25 | test | |
000108 16:42:05 | 000108 16:53:14 | 0 | 11 | postmortem | |
| | | | | |
Table 5-6. Time Recording Log
Student: | Victor Putz | Date: | 000108 |
| | Program: | 5a |
Start | Stop | Interruption Time | Delta time | Phase | Comments |
000108 15:19:51 | 000108 15:51:15 | 0 | 31 | code | |
000108 15:51:17 | 000108 16:03:51 | 0 | 12 | compile | |
000108 16:04:00 | 000108 16:10:14 | 0 | 6 | test | |
| | | | | |
Table 5-7. Defect Recording Log
Student: | 5A | Date: | 000108 |
| | Program: | Victor Putz |
Defect found | Type | Reason | Phase Injected | Phase Removed | Fix time | Comments |
000108 14:08:47 | md | ig | design | code | 9 | added separate feature for single_pass of simpson procedure
|
000108 14:18:26 | md | ig | design | code | 0 | added is_odd feature
|
000108 14:29:02 | md | ig | design | code | 1 | didn't design to handle different functions. Probably unnecessary, but it's more modular
|
000108 14:39:50 | sy | om | code | compile | 0 | forgot to change header inclusion
|
000108 14:40:31 | sy | ty | code | compile | 0 | Typo in string constant
|
000108 14:40:58 | sy | om | code | compile | 0 | Forgot to add const to implementation
|
000108 14:42:04 | iu | cm | code | compile | 0 | Incorrect order of arguments
|
000108 14:43:25 | sy | ty | code | compile | 0 | forgot to add const to implementation
|
000108 14:43:50 | wn | cm | code | compile | 0 | wrong name of a variable
|
000108 14:44:49 | wt | ig | code | compile | 0 | tried to "constify" a member and then modify it
|
000108 14:45:48 | sy | ty | code | compile | 0 | forgot to add "single_function_input_parser::" to constructor
|
000108 14:46:42 | md | om | design | compile | 0 | forgot to assign a normal integral to the input parser
|
000108 14:49:02 | sy | ty | code | compile | 0 | Missed comma in constructor list
|
000108 14:49:26 | sy | om | code | compile | 0 | missed more consts in implementation
|
000108 14:50:10 | sy | ty | code | compile | 0 | attempted eiffel-style assignment (:=) in feature
|
000108 14:50:44 | sy | ty | code | compile | 0 | misspelled variable name
|
000108 14:51:22 | wt | cm | code | compile | 1 | used "double" as argument in is_odd; meant to use int
|
000108 14:54:09 | wa | ig | code | test | 18 | check was for double equality; should have used difference < margin
|
000108 15:13:26 | wa | om | code | test | 3 | some confusion in boolean check conditions to continue the simpson process
|
| | | | | | |
Table 5-8. Defect Recording Log
Student: | Victor Putz | Date: | 000108 |
| | Program: | 5a |
Defect found | Type | Reason | Phase Injected | Phase Removed | Fix time | Comments |
000108 15:51:40 | sy | ty | code | compile | 0 | forgot "end" at end of class
|
000108 15:52:09 | ma | om | code | compile | 0 | forgot to instantiate the parser
|
000108 15:53:01 | ma | om | code | compile | 0 | forgot to create a function and assign it to the parser
|
000108 15:53:57 | sy | ty | code | compile | 0 | put inherit and creation clauses in wrong order
|
000108 15:54:15 | sy | ty | code | compile | 0 | used c-style == to check equality
|
000108 15:55:23 | ma | cm | code | compile | 2 | forgot to instantiate f and call its creation clause
|
000108 15:59:00 | sy | om | code | compile | 0 | forgot to declare a return type for integral
|
000108 16:00:14 | ma | om | code | compile | 0 | forgot to call make on new object
|
000108 16:00:59 | sy | ty | code | compile | 0 | forgot to put class name in all caps
|
000108 16:01:40 | sy | om | code | compile | 0 | incorrectly declared once function
|
000108 16:03:28 | sy | om | code | compile | 0 | forgot to add third_width to local declaration
|
000108 16:04:11 | ma | om | code | test | 1 | forgot to increment loop index... again...
|
000108 16:08:22 | ma | om | code | test | 1 | forgot to update has_been_calculated flag
|
| | | | | | |