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

C++ For Mathematicians (2006) [eng]

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

256

C++ for Mathematicians

5#include "Mod.h"

6

7const long max_bits = 31;

8

9Polynomial<Mod> long2poly(long m);

10

11 #endif

This header defines a constant max_bits that sets an upper bound on d; this value is based on the size of a long integer on the computer on which this program is to be run.

The procedure takes a long integer argument and returns a Polynomial<Mod>. To write this program, we want to access the individual bits of the integer argument, m. The way we do this is to check if m is even or odd, and then set b0 accordingly. We then divide m by 2, check if the result is even or odd, and then set d1. We continue in this fashion until m is zero. Here is the code.

Program 12.6: Code file for the long2poly procedure.

1#include "long2poly.h"

2

3 Polynomial<Mod> long2poly(long m) {

4Polynomial<Mod> ans;

5

6long j = 0;

7while (m != 0) {

8 ans.set(j, Mod(m,2));

9m /= 2;

10j++;

11}

12

13return ans;

14}

Next, we need a main to implement the exhaustive algorithm.

Program 12.7: Main program for the GCD revisited problem.

1 #include "Polynomial.h"

2#include "Mod.h"

3#include "long2poly.h"

4

5using namespace std;

6

7 int main() {

8long d;

9 cout << "Enter degree bound --> ";

10 cin >> d;

11

12if ( (d<1) || (d>max_bits) ) {

13cerr << "Please choose d between 1 and " << max_bits << endl;

14return 0;

15}

16

Polynomials

257

17 long bound = 1<<d;

18

19 Polynomial<Mod> *list;

20

21 list = new Polynomial<Mod>[bound];

22

23cerr << "Generating polynomials ... ";

24for (long k=0; k<bound; k++) {

25list[k] = long2poly(k);

26}

27cerr << "done! " << endl << bound

28<< " polynomials of degree less than "

29<< d <<" generated" << endl;

30

31long count = 0;

32const Polynomial<Mod> one(Mod(1,2));

33

34for (long i=0; i<bound-1; i++) {

35for (long j=i+1; j<bound; j++) {

36if( gcd(list[i],list[j]) == one ) count++;

37}

38}

39

40 count = 2*count + 1;

41

42cout << count << " out of " << bound*bound

43<< " pairs are relatively prime" << endl;

44

45 cout << count / (double(bound) * double(bound)) << endl;

46

47 return 0;

48 }

 

 

Finally, when the program is run, we see the following.

 

 

 

 

 

Enter degree bound --> 10

 

 

 

 

 

 

 

Generating polynomials ... done!

 

 

 

 

 

1024 polynomials of degree less than 10 generated

 

 

 

 

 

524289 out of 1048576 pairs are relatively prime

 

 

 

 

 

0.500001

 

 

 

 

 

 

 

The formulation of a conjecture, and its proof,1 are left as an exercise for the reader.

1For a proof via generating functions, see S. Corteel, C. Savage, H. Wilf, D. Zeilberger, A pentagonal number sieve, Journal of Combinatorial Theory, Series A 82 (1998) 186–192. Recently, Art Benjamin and Curtis Bennett have found a bijective proof (submitted for publication).

258

C++ for Mathematicians

12.5Working in binary

The long2poly procedure used a trick to convert a long integer m into polynomials p(x) in Z2[x]. We set the constant coefficient of p(x) based on the parity of m, and then we divided m by 2 (keeping only the integer part). We then repeated this technique to set higher and higher coefficients until m vanished. In short, we used division arithmetic to read off the base-2 digits of m.

In other words, we used a mathematical trick to find the binary representation of m. However, the binary is already present inside the computer; it is more efficient to work directly with that. C++ provides a family of operators for working directly on the bits in the binary form of integers.

12.5.1 Signed versus unsigned integers

Integers are stored inside the computer in binary. The number 20 is represented internally as 0000000000010100.

In this, and subsequent examples, we assume the integers are held as short types; on my computer these are two bytes (16 bits) long. Other integer types may have 32 or 64 bits.

The storage of negative integers is mildly counterintuitive. The leftmost bit is known as the sign bit. If this bit is 1, the number represented is negative. However, −20 is not represented as 1000000000010100. Look closely at the correct internal representation of −20 and 19:

Value

Binary representation

−20

1111111111101100

19

0000000000010011

For a positive integer n, the binary representation of n is just the usual base-2 representation. However, the binary representation of −n is formed by complementing the bits of n −1. Of course, zero is represented by an all-zero binary number.

This manner of storing negative values is known as the twos complement representation. This representation is used for the sake of computational efficiency.

The integer types (char, short, int, and long) all have variants that restrict their range to nonnegative values. These variant types prepend the word unsigned to the type name. For example:

unsigned short x;

To illustrate the difference, suppose we have two variables x and y declared thus:

unsigned short x; short y;

Suppose both of these hold the bits 1111111111111111. In this case, the value of x is 65,535 (216 −1) whereas the value of y is −1.

Polynomials

259

12.5.2 Bit operations

C++ provides six operators for working with the binary representation of integers.

Bitwise and For integer variables x and y, the expression x&y is the bitwise and of x and y. That is, the kth bit of x&y is 1 if and only if the kth bits of both x and y are 1. Here is an example.

x

0100001101100000

y

0001000111101101

 

 

x&y

0000000101100000

The bitwise and operation & should not be confused with the Boolean and operation &&. You should use && only with bool values.

Bitwise or Similar to bitwise and, the operation x|y gives the bitwise or of x and y. That is, the kth bit of x|y is 0 if and only if the kth bits of both x and y are 0. Here is an example.

x

0100001101100000

y

0001000111101101

x|y

0101001111101101

The bitwise or operation | should not be confused with the Boolean or operation ||. You should use || only with bool values.

Exclusive or The expression xˆy gives the bitwise exclusive or of x and y. That is, the kth bit of x|y is 0 if and only if exactly one of the kth bits of both x and y is 1. Here is an example.

x

0100001101100000

y

0001000111101101

xˆy

0101001010001101

 

 

Bitwise not The expression ˜x interchanges 1s and 0s in x. That is, the kth bit of ˜x is 1 if and only if the kth bit of x is 0. Here is an example.

x

0100001101100000

 

 

˜x

1011110010011111

 

 

The bitwise not operation ˜ should not be confused with the Boolean not operation !. You should use ! only with bool values.

Left shift The expression x<<n (where n is a nonnegative integer) shifts the bits of x to the left n steps. The right-hand side of the result is filled with 0s. Any bits in the highest n positions are lost. Here is an example.

x

0100001101100000

x<<5

0110110000000000

 

 

260

C++ for Mathematicians

The symbol << is the same one we use for writing to the console, as in the statement cout << x << endl;. C++ is able to distinguish between these cases by inspecting the types of objects on either side of the << symbol.

The expression x<<n is equivalent to multiplying x by 2n (unless bits are lost at the left).

Right shift The expression x>>n (where n is a nonnegative integer) shifts the bits of x to the right n places. Bits in the lower n places are lost. The vacated positions on the left are filled in with 0s or with 1s depending on the situation:

If x is an unsigned integer type, 0s are inserted at the left.

If x is a signed integer type and x is nonnegative, 0s are inserted at the left.

If x is a negative integer, then 1s are inserted at the left. Here are some examples.

short x

0010010010001010

x>>5

0000000100100100

 

 

unsigned short y

1000110010110111

y>>5

0000010001100101

 

 

short z

1000110010110111

z>>5

1111110001100101

 

 

The right shift operator >> uses the same symbol we use for keyboard input, for example, cin >> x;. As with left shift, C++ distinguishes these cases by inspecting the types of the objects on either side of the >> symbol.

All six of these operators can be combined with the assignment operator, =. The expression x &= y is equivalent to x = (x&y), and so on.

Bit operations can be combined to perform operations that would be difficult with standard mathematical operators. For example, suppose we want to set the kth bit of x to 1; the following code does the trick: x |= (1<<k);. If we want to set that bit to zero, we do this: x &= ˜(1<<k);.

12.5.3 The bitset class template

Using integer types to represent a list of binary values is efficient, but presents two drawbacks. First, this technique is limited to the size of an integer on your computer; if you want a list of, say, 200 bits, there is no integer type with that capacity. Second, using bit manipulation can result in obfuscated code. Human beings find statements such as x&=˜(1<<k); difficult to understand. (The statement sets the kth bit of x to zero.) If your problem requires high speed for short lists of bits, then bit manipulation of integer types may be your best option. However, there are two other choices of which you should be aware.

The first option, also discussed in Section 8.4, is to use vector<bool> variables; these are adjustable arrays of true/false values. To set the kth bit of such an

Polynomials

261

array equal to zero (false), we use the considerably clearer statement x[k]=0; or x[k]=false;. Variables of type vector<bool> use memory efficiently, can be easily resized, and provide convenient access to their elements. However, the bitwise operations (such as &, ˜, >>, etc.) cannot be used with variables of type vector<bool>. If one wished to interchange all the 0s and 1s held in x, the statement x=˜x; does not work. Instead, one would need to write a for loop to change the bits one by one:

for (int k=0; k<x.size(); k++) { x[k] = !x[k];

}

The second option is to use a bitset. A bitset object is a fixed size repository of bits. To use variables of type bitset, start with #include <bitset> and declare variables like this:

bitset<100> x;

This sets up x as a list of 100 bits. Notice that bitset is a template but its argument is a number, not a type; we explain how to do this later in this section. The important point is that this number is a constant, not a variable. The following code is illegal.

int n;

cout << "Enter number of bits --> "; cin >> n;

bitset<n> x; // illegal constructor, n is not a constant

The size of the bitset must be declared when you write your program, not while the program is running.

Here is a list of the various methods and operators available for bitsets.

• Constructors. The standard constructor has the form

bitset<number> var;

where number is a specific positive integer. This may be a const int defined earlier in the code, or an explicitly typed value, such as 100. The variable var holds number bits, and at this point these are all 0s.

One may construct from an unsigned long integer value. For example,

bitset<20> x(39);

sets x to 00000000000000100111 (the binary representation of 39).

One may also construct from a C++ string object (these are discussed later in Section 14.2). The constructor

bitset<20> x(string("10110001"));

sets x to 00000000000010110001. The type string is required; don’t use bitset<20> x("10110001"));.

Finally, a copy constructor is available:

262

C++ for Mathematicians

bitset<20> y(x);

makes y a copy of x. Note that x must also be a bitset<20> and may not be a bitset of any other size.

Inspection methods. These are methods one can use to learn information about the bits held in a bitset. Suppose x is a bitset<100>:

x.size() returns the number of bits that x holds (in this example, 100).

x.any() returns true if at least one bit in x is a 1.

x.none() returns true if all of the bits are 0.

x.count() returns the number of 1s in x.

x.test(k) returns true if the kth bit of x is a 1. Of course, k must be at least 0 and less than x.size().

Bit manipulation methods. The following methods may be used to alter the value held in the bits of a bitset. Suppose x is a bitset<100>:

x.set() sets all of x’s bits to 1.

x.set(k) sets the kth bit to 1.

x.set(k,b) sets the kth bit base on the value held in the integer variable b. If b is zero, the kth bit of x is set to 0; otherwise it is set to 1.

x.reset() sets all of x’s bits to 0.

x.reset(k) sets bit k to 0.

x.flip() swaps 0 and 1 values in every position of x. For example, suppose x holds 1110001110; after the statement x.flip();, it now holds 0001110001.

x.flip(k) flips the kth bit of x.

Comparison operators. If x and y are both bitsets of the same size, then we may compare them with the usual expressions x==y and x!=y.

Bit operators. The standard bitwise operators (&, |, ˆ, ˜, <<, >>) and their assignment variants (&=, |=, ˆ=, ˜=, <<=, >>=) may be used on a pair of bitsets of the same size.

Array style access. In addition to the methods described above, individual elements of a bitset may be accessed using square brackets. The expression x[k] is the kth element of x. The expression x[k] may appear on the right or the left of an assignment statement such as x[4]=˜x[10];.

In addition, the expression x[5].flip() is equivalent to x.flip(5); both of these toggle the fifth bit of x.

Polynomials

263

Input/output. Objects of type bitset can be written to the computer’s screen and read from the keyboard.

The statement cin >> x; reads a sequence of 0s and 1s from the keyboard. At most x.size() bits are read. If fewer bits are read (before reaching a character other than 0 or 1), the left bits are filled with zeros.

The statement cout << x; prints x to the screen. The highest bit (in position x.size()-1) is printed first and the lowest bit, x[0], is printed last.

Here is a short program that illustrates these ideas.

#include <bitset> #include <iostream> using namespace std;

int main() { bitset<10> x;

cout << "Enter bits -> "; cin >> x;

cout << "x = " << x << endl; for (int k=0; k<10; k++) {

cout << "x[" << k << "] = " << x[k] << endl;

}

return 0;

}

Here is a sample run of this program.

Enter bits -> 1101 x = 0000001101 x[0] = 1

x[1] = 0 x[2] = 1 x[3] = 1 x[4] = 0 x[5] = 0 x[6] = 0 x[7] = 0 x[8] = 0x[9] = 0

12.5.4 Class templates with non-type arguments

We have seen a variety of templates, and in nearly all cases the arguments to the template, given between the < and > delimiters, are C++ types. For example, the complex class template is completed with a numeric type (e.g., complex<double>) and our max_of_three procedure template may use any type arguments that can be compared with < (e.g., three PTriple values).

The exception is the bitset class template. Here, the template is completed by specifying an unsigned integer value. How is this done?

A typical class template is defined in a header file like this:

264

C++ for Mathematicians

template <class T>

 

MyTemplateClass {

 

.....

 

};

 

where the data and methods in MyTemplateClass may refer to variables of type T. Variables are declared with statements such as this.

MyTemplateClass<double> x;

However, the template parameters (the arguments between < and > delimiters) need not be classes. For example, we could create a class template such as this:

template <long N> AnotherClass {

.....

};

The parameter N may appear in the data and methods of AnotherClass wherever a constant long integer might rightly go. For example, AnotherClass might include a data member that is an array declared like this:

private:

double coordinates[N];

Note that AnotherClass<10> and AnotherClass<11> are different classes (although based on the same template).

Template classes may have multiple parameters that may be classes or specific values as in this example:

template <class T, double X, int N, class S> ComplicatedClass {

.....

};

A declaration based on this template would look like this:

ComplicatedClass<int, -3.5, 17, double> x;

12.6Exercises

12.1Exercise 7.3 (page 126) asks you to create a procedure to find the median of a list of real (double) numbers. Make a new version that can handle numbers of any type by using a template. The procedure should not modify the array.

12.2Exercise 11.5 (page 233) asks you to create the class SmartArray that allows arbitrary indexing (e.g., a −1 index returns the last element of the array). In that problem, a SmartArray contains long integers. Create a new version of SmartArray that can hold values of any given type. For example, to declare a SmartArray to hold 20 double values, you would type this:

Polynomials

265

SmartArray<double> X(20);

12.3Create a derivative procedure that finds the derivative of a Polynomial.

12.4Create a root-finding procedure for polynomials based on Newton’s method. Given a polynomial p and initial guess x0, the procedure should solve p(x) = 0 using the iteration

p(xk) xk+1 = xk p0(xk) .

How many iterations should be performed? You may either let the user set the number of iterations or a desired tolerance ε so that |p(x)| < ε.

Be sure to address the following issues.

The polynomial may be either real or complex.

The initial x0 may be either real or complex.

The roots might not be simple (i.e., the roots might have multiplicity greater than 1).

12.5The pair class template (defined in the utility header) is a handy mechanism for creating ordered pairs; see Section 8.5. Using pair as an inspiration, create a triple class template that represents an ordered three-tuple (x,y,z) where the three elements may be of any type (including different types from each other). Make the data fields public and name them first, second, and third.

Remember to define an operator< that orders the triples lexicographically.

In addition, provide a make_triple procedure procedure that is analogous to make_pair.

12.6Create a RationalFunction class template to represent rational functions. [A rational function is a quotient of two polynomials, p(x)/q(x).] The coeffi-

cients may be real, complex, or from Zp for some prime p. Include the basic operations +, −, ×, ÷.

12.7Write a program to print out all subsets of the set {1,2,...,n}. Do this with an integer variable that steps from 0 to 2n −1 and convert that value into a set.