Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C++ For Mathematicians (2006) [eng]

.pdf
Скачиваний:
211
Добавлен:
16.08.2013
Размер:
31.64 Mб
Скачать

196

C++ for Mathematicians

For any C++ procedure (class method or free-standing procedure), default values can be specified. In the .h file, where procedures are declared, we use a syntax such as this:

return_type procedure_name(type arg1 = val1, type arg2 = val2, ...);

Then, when the procedure is used, any missing parameters are replaced by their default values. Let’s look at a concrete example. We declare a procedure named next that produces the next integer after a given integer. (This is a contrived example, but we want to keep things simple.) In the header file we put the following,

int next(int num, int step=1);

And in the .cc file, we have the code,

int next(int num, int step) { return num+step;

}

Notice that the argument step is given a default value, 1. However, the argument num is not given a default. It is permissible to specify only a subset of the arguments that receive default values, but if an argument has a default value, all arguments to its right must also have default values.

Notice that the optional arguments are not reiterated in the .cc file. Consider the following code.

int

a,b,c;

a =

next(5);

b

=

next(5,1);

c

=

next(5,2);

This will set a and b equal to 6 and c equal to 7.

Alternatively, we could have given an inline definition of next in the header file like this:

inline int next(int num, int step=1) { return num+step; }

Returning to PPoint, the four constructors (with zero to three double arguments) can all be declared at once like this:

PPoint(double a=0., double b=0., double c=1.) ......

See line 10 of Program 10.6. The action of this constructor is simply to pass the three arguments up to its parent (line 11) and then there is nothing else to do. To show there is nothing else, we give a pair of open/close braces that enclose empty space (line 12).

The class PPoint needs one more constructor. Recall that PObject provides methods such as rand_perp and op that return PObjects. However, when these are used by PPoint or PLine, the PObject values need to be converted to type PPoint or PLine as appropriate. To do this PPoint provides a constructor that accepts a single argument of type PObject. Here is the simple code (see also lines 14–15 of Program 10.6).

The Projective Plane

197

PPoint(const PObject& that) :

PObject(that.getX(), that.getY(), that.getZ()) { }

This code sends the x, y, and z values held in that up to the parent constructor and then does nothing else. Now, if we want to assign a PObject value to a PPoint object, we can do it in the following ways.

PObject X(2.,3.,5.);

PPoint

P(X);

// invoke the

constructor at

declaration of P

PPoint

Q;

 

 

 

Q

=

PPoint(X);

// invoke the

constructor as

a converter procedure

PPoint

R;

 

 

 

R

=

X;

// implicit conversion (compiler figures out what to do)

Here is the PPoint.h header file.

Program 10.6: Header file for the PPoint class.

1#ifndef PPOINT_H

2#define PPOINT_H

3

4 #include "PObject.h"

5

6 class PLine;

7

8class PPoint : public PObject {

9public:

10PPoint(double a = 0., double b = 0., double c = 1.) :

11PObject(a,b,c)

12{ }

13

14PPoint(const PObject& that) :

15PObject(that.getX(), that.getY(), that.getZ()) { }

16

17bool operator==(const PPoint& that) const {

18return equals(that);

19}

20

21bool operator!=(const PPoint& that) const {

22return !equals(that);

23}

24

25bool operator<(const PPoint& that) const {

26return less(that);

27}

28

29 PLine operator+(const PPoint& that) const;

30

31 bool is_on(const PLine& that) const;

32

33 PLine rand_line() const;

34

35 };

198

C++ for Mathematicians

36

37 ostream& operator<<(ostream& os, const PPoint& P);

38

39inline bool

40collinear(const PPoint& A, const PPoint& B, const PPoint& C) {

41return dependent(A,B,C);

42}

43

44 #endif

On lines 17–26 we give inline definitions of operator==, operator!=, and operator<. However, operator+, is_on, and rand_line may not be given inline because these refer to the (as yet) unknown class PLine. (See lines 29–33.)

The collinear procedure simply invokes the dependent procedure defined in PObject.h, so we give it inline here. The inline keyword in mandatory here because collinear is not a member of any class.

The parts of PPoint not given in PPoint.h are defined in PPoint.cc which we present next.

Program 10.7: Program file for the PPoint class.

1 #include "Projective.h"

2

3ostream& operator<<(ostream& os, const PPoint& P) {

4os << "(" << P.getX() << "," << P.getY() << "," << P.getZ() << ")";

5return os;

6}

7

8PLine PPoint::operator+(const PPoint& that) const {

9return PLine(op(that));

10}

11

12PLine PPoint::rand_line() const {

13return PLine(rand_perp());

14}

15

16bool PPoint::is_on(const PLine& that) const {

17return incident(that);

18}

The code for the class PLine is extremely similar to that of PPoint. Here are the files PLine.h and PLine.cc for your perusal.

Program 10.8: Header file for the PLine class.

1#ifndef PLINE_H

2#define PLINE_H

3

 

4

#include "PObject.h"

5

 

6

class PPoint;

7

 

8

class PLine : public PObject {

The Projective Plane

199

9public:

10PLine(double a = 0., double b = 0., double c = 1.) :

11PObject(a,b,c)

12{ }

13

14PLine(const PObject& that) :

15PObject(that.getX(), that.getY(), that.getZ()) { }

16

17bool operator==(const PLine& that) const {

18return equals(that);

19}

20

21bool operator!=(const PLine& that) const {

22return !equals(that);

23}

24

25bool operator<(const PLine& that) const {

26return less(that);

27}

28

29 PPoint operator*(const PLine& that) const;

30

31 bool has(const PPoint& X) const;

32

33 PPoint rand_point() const;

34

35 };

36

37 ostream& operator<<(ostream& os, const PLine& P);

38

39inline bool

40concurrent(const PLine& A, const PLine& B, const PLine& C) {

41return dependent(A,B,C);

42}

43

44 #endif

Program 10.9: Program file for the PLine class.

1 #include "Projective.h"

2

3ostream& operator<<(ostream& os, const PLine& P) {

4os << "[" << P.getX() << "," << P.getY() << "," << P.getZ() << "]";

5return os;

6}

7

8PPoint PLine::rand_point() const {

9return PPoint(rand_perp());

10}

11

12PPoint PLine::operator*(const PLine& that) const {

13return PLine(op(that));

14}

15

16 bool PLine::has(const PPoint& that) const {

200

C++ for Mathematicians

17return incident(that);

18}

10.8 Discovering and repairing a bug

With the projective point and line classes built, it is time to test our code. Here is a simple main to perform some checks.

Program 10.10: A main to test the RP2 classes.

1#include <iostream>

2#include "Projective.h"

3#include "uniform.h"

4

5int main() {

6seed();

7PPoint P;

8

9P.randomize();

10cout << "The random point P is " << P << endl;

11

12 PLine L,M;

13

14L = P.rand_line();

15M = P.rand_line();

16

17cout << "Two lines through P are L = " << L << endl

18<< "and M = " << M << endl;

19

20cout << "Is P on L? " << P.is_on(L) << endl;

21cout << "Does M have P? " << M.has(P) << endl;

22

23PPoint Q;

24Q = L*M;

26 cout << "The point of intersection of L and M is Q = " << Q << endl;

27

28cout << "Is Q on L? " << Q.is_on(L) << endl;

29cout << "Does M have Q? " << M.has(Q) << endl;

30

31if (P==Q) {

32cout << "P and Q are equal" << endl;

33}

34else {

35cout << "P and Q are NOT equal" << endl;

36}

37

38return 0;

39}

 

The Projective Plane

201

 

 

When this program is run, we have the following output:

 

 

 

 

 

The random point P is (-1.32445,0.591751,1)

 

 

 

 

 

 

Two lines through P are L = [6.51303,12.8875,1]

 

 

 

and M = [0.871229,0.260071,1]

 

 

 

Is P on L? 0

 

 

 

Does M have P? 0

 

 

 

The point of intersection of L and M is Q = (-1.32445,0.591751,1)

 

 

 

Is Q on L? 0

 

 

 

Does M have Q? 0

 

 

 

P and Q are NOT equal

 

 

 

 

 

Much of what we see here doesn’t make sense. First, the lines L and M are random lines through P. Yet the output indicates that P is on neither of these lines. Then, we generate the point Q at the intersection of L and M. The good news is that the coordinates of P and Q are the same: (5.64488,2.562,1). However, the computer still tells us that Q is on neither L nor M. Worse, it thinks that P = Q. What is going on here!?

All these problems stem from the same underlying cause: roundoff. Remember that a double variable is a rational approximation to a real number. Two real quantities that are computed differently may result in double values that are different. For example, consider this code:

#include <iostream> using namespace std; int main() {

double x

= 193./191.;

double y

= 1./191.;

y *= 193;

 

if (x ==

y) {

cout << "They are equal" << endl;

}

else {

cout << "They are different" << endl; cout << "Difference = " << x-y << endl;

}

return 0;

}

 

Here is the output from this program.

 

 

 

 

 

 

 

They are different

 

 

 

 

Difference = -2.22045e-16

 

 

 

 

 

 

 

The computer reports that 191193 = 193 ×

1

.

 

 

191

 

 

The equality test we created for PObjects checks if the three coordinates are

 

 

exactly the same. We need to relax this.

 

 

The bad news is we need to rewrite some of our code to correct this problem. The

 

 

great news is that we only need to repair PObject. The children PPoint and PLine

 

 

inherit the improvements.

 

202

C++ for Mathematicians

To begin, let us identify the places in the code for PObject where exact equality is sought.

The equals method requires exact equality of the three coordinates.

The incident method requires zero to be the exact result of the dot product method.

The dependent procedure requires zero to be the exact value of the determinant.

In lieu of exact equality, we can require that the values be within a small tolerance. What tolerance should we use? We make that quantity user selectable initialized with some default value (say 1012).

To implement this idea we add a private static double variable named tolerance and define a constant named default_tolerance set to 1012.

Inside PObject we define two inline public static methods: set_tolerance and get_tolerance. Here is the revised header file.

Program 10.11: Header file for the PObject class (version 2).

1#ifndef POBJECT_H

2#define POBJECT_H

3#include <cmath>

4#include <iostream>

5using namespace std;

6

7 const double default_tolerance = 1e-12;

8

9class PObject {

10private:

11double x,y,z;

12void scale();

13double dot(const PObject& that) const;

14static double tolerance;

15

16protected:

17bool equals(const PObject& that) const;

18bool less(const PObject& that) const;

19bool incident(const PObject& that) const;

20PObject rand_perp() const;

21PObject op(const PObject& that) const;

22

23public:

24PObject() {

25x = y = 0.;

26z = 1.;

27}

28PObject(double a, double b, double c) {

29x = a;

30y = b;

31z = c;

32scale();

The Projective Plane

203

33}

34void randomize();

35

36static void set_tolerance(double t) {

37tolerance = abs(t);

38}

39

40static double get_tolerance() {

41return tolerance;

42}

43

44double getX() const { return x; }

45double getY() const { return y; }

46double getZ() const { return z; }

47

48bool is_invalid() const {

49return (x==0.) && (y==0.) && (z==0.);

50}

51

52 };

53

54 ostream& operator<<(ostream& os, const PObject& A);

55

56 bool dependent(const PObject& A, const PObject& B, const PObject& C);

57

58 #endif

Inside PObject.cc we need to declare PObject::tolerance and we give it an initial value. (See Program 10.12, line 4.)

We also need to modify the equals, incident, and dependent procedures to test for near equality instead of exact equality. You can find these modifications on lines 37–39, 52, and 80 of the new PObject.cc file which we present here.

Program 10.12: Program file for the PObject class (version 2).

1#include "PObject.h"

2#include "uniform.h"

3

4 double PObject::tolerance = default_tolerance;

5

6void PObject::scale() {

7if (z != 0.) {

8x /= z;

9y /= z;

10z = 1.;

11return;

12}

13if (y != 0.) {

14x /= y;

15y = 1.;

16return;

17}

18if (x != 0) {

19x = 1.;

20}

204

C++ for Mathematicians

21 }

22

23double PObject::dot(const PObject& that) const {

24return x*that.x + y*that.y + z*that.z;

25}

26

27void PObject::randomize() {

28do {

29x = unif(-1.,1.);

30y = unif(-1.,1.);

31z = unif(-1.,1.);

32} while (x*x + y*y + z*z > 1.);

33scale();

34}

35

36bool PObject::equals(const PObject& that) const {

37double d = abs(x-that.x) + abs(y-that.y) + abs(z-that.z);

38

39return d <= tolerance;

40}

41

42bool PObject::less(const PObject& that) const {

43if (x < that.x) return true;

44if (x > that.x) return false;

45if (y < that.y) return true;

46if (y > that.y) return false;

47if (z < that.z) return true;

48return false;

49}

50

51bool PObject::incident(const PObject& that) const {

52return abs(dot(that)) <= tolerance;

53}

54

55ostream& operator<<(ostream& os, const PObject& A) {

56os << "<"

57<< A.getX() << ","

58<< A.getY() << ","

59<< A.getZ()

60<< ">";

61return os;

62}

63

64bool dependent(const PObject& A, const PObject& B, const PObject& C){

65double a1 = A.getX();

66double a2 = A.getY();

67double a3 = A.getZ();

68

69double b1 = B.getX();

70double b2 = B.getY();

71double b3 = B.getZ();

72

73double c1 = C.getX();

74double c2 = C.getY();

75double c3 = C.getZ();

76

 

 

 

The Projective Plane

205

77

double det =

a1*b2*c3

+

a2*b3*c1

+

a3*b1*c2

 

78

-

a3*b2*c1

-

a1*b3*c2

-

a2*b1*c3;

 

79

 

 

 

 

 

 

 

80return abs(det) <= PObject::get_tolerance();

81}

82

83PObject PObject::rand_perp() const {

84if (is_invalid()) return PObject(0,0,0);

85

 

 

 

86

double x1,y1,z1;

//

One vector orthogonal to (x,y,z)

87

double x2,y2,z2;

//

Another orthogonal to (x,y,z) and (x1,y1,z1)

88

 

 

 

89if (z == 0.) { // If z==0, take (0,0,1) for (x1,y1,y2)

90x1 = 0;

91y1 = 0;

92z1 = 1;

93}

94else {

95if (y == 0.) { // z != 0 and y == 0, use (0,1,0)

96x1 = 0;

97y1 = 1;

98z1 = 1;

99}

100else { // y and z both nonzero, use (0,-z,y)

101x1 = 0;

102y1 = -z;

103z1 = y;

104}

105}

106

107// normalize (x1,y1,z1)

108double r1 = sqrt(x1*x1 + y1*y1 + z1*z1);

109x1 /= r1;

110y1 /= r1;

111z1 /= r1;

112

113// (get x2,y2,z2) by cross-product with (x,y,z) and (x1,y1,z1)

114x2 = -(y1*z) + y*z1;

115y2 = x1*z - x*z1;

116z2 = -(x1*y) + x*y1;

117

118// normalize (x2,y2,z2)

119double r2 = sqrt(x2*x2 + y2*y2 + z2*z2);

120x2 /= r2;

121y2 /= r2;

122z2 /= r2;

123

124// get a point uniformly on the unit circle

125double a,b,r;

126do {

127a = unif(-1.,1.);

128b = unif(-1.,1.);

129r = a*a + b*b;

130} while (r > 1.);

131r = sqrt(r);

132a /= r;