Numerical Operations

C++ is particularly powerful at engineering and scientific applications, which require intensive numerical calculations. There is the full range of mathematical functions inherited from C, now including versions that work with complex numbers. There are standard algorithms defined in <numeric>, which do full and partial sums of sequences. There is also the valarray class, which is optimized for high-performance applications and works like a mathematical vector.

Mathematical Functions: <cmath>

All the mathematical functions take double arguments and return double values. (In addition, C++ makes complex versions available.) Be careful with functions like sqrt(), log(), and pow() that are not defined for all values of x; this is called a domain error. For example, it is an error to pass a negative real value to sqrt().

When calculating angles from values of the tangent, you should use atan2() rather than atan() because it provides an answer that is not ambiguous; the answer is in the range –pi to pi, which is the full circle.

sin(x);   // sine of x (all angles in radians)
cos(x);   // cosine of x
tan(x);   // tangent of x
asin(x);  // arc sine of x (returns radians)
acos(x);  // arc cos
atan(x);  // arc tan
atan2(y,x); // arc tangent of y/x (better than atan(x)!)
pow(x,y);   // x to the power of y
sqrt(x);    // square root of x
log(x);   // ln(x) (natural logarithm)
log10(x); // log(x) (base 10 logarithm)
exp(x);   // exponential function; same as pow(e,x)
ceil(x);  // truncate upwards to nearest integer
floor(x);  // truncate downwards to nearest integer
fabs(x);   // absolute value of x

In addition to the hyperbolic trigonometric functions (such as sinh()), most systems have the various Bessel functions, although these are not part of the ANSI standard.

Standard Numerical Algorithms: <numeric>

The following are specialized for numerical data and are found in <numeric>. These algorithms all work on sequences of numbers; accumulate() will sum the values, but you must give an initial value. partial_sum() will generate the running sum (for example, (0,1,2,4) becomes (0,1, 2+1, 4+2+1)), and adjacent_difference() will give the differences. Applying partial_sum() and then adjacent_difference() will give you back the original sequence. Please look at the stock statistics case study at the end of Chapter 3, “Arrays and Algorithms,” for examples of these algorithms in action.

// sum of the elements in (i1,i2)
T accumulate(In i1, In i2, T val);
// put running sum of elements of (i1,i2) into j
Out partial_sum(In i1, In i2, Out j);
// put differences between elements of (i1,i2) into j
Out adjacent_difference(In i1, In i2, Out j);

Complex Numbers <complex.h>

One of the reasons scientific programmers preferred FORTRAN to C was because FORTRAN explicitly supported complex numbers. The standard C++ library defines a complex type that can be just as efficient as C or FORTRAN code.

A complex number has a real part and an imaginary part, and it is essential in most parts of science and engineering because it is the most general form of number; it's no exaggeration to say that normal floating-point real numbers are a special case of complex numbers. For example, here is an example program demonstrating operations on C++ complex numbers.


// test-complex.cpp
#include <iostream>
#include <complex.h>
using namespace std;

typedef complex<double> Complex;
const double PI = 3.14123;

int main()
{
 Complex x(0,1);               // the square root of -1
 Complex y = x*x;              // sure enough, y is now (-1,0)
 cout << "y = " << y << endl;  // can write (or write) as numbers
 y = sqrt(x);                  // can apply the usual functions
 x = pow(y,2.0);
 y = sin(x);
 x = conj(y);                 // the complex conjugate of y
 y = polar(1.0, PI/2);        // a complex number of length 1, argument pi/2
 x = 2.0;                     // can initialize with a double
// Can access the real or imaginary parts
 cout << x.real() << ' ' << y.imag() << endl;
// Or in polar coordinates: a length and an angle.
 cout << abs(y) << ' ' << arg(y) << endl;
}

This produces the following output:


y = (-1,0)
2 1
1 1.57062

The usual operations (that is, -, +, *, /, ==, !=) are available, as are the standard scientific functions (that is, sin(), cos(), tan(), sinh(), cosh(), tanh(), exp(), log(), log10()).

The valarray Class: <valarray>

The valarray type is like vector, but it is designed for fast arithmetic operations. It is in fact very much like a mathematical vector; you can multiply it by a scalar (that is, a single real number), add a valarray to another, apply mathematical functions, and so on, as in the following example:

#include <valarray>
...
#define FOR(i,n) for(int i = 0; i < n; i++)
valarray<double> v1(20), v2(20), v3(20);
FOR(i,20) v1[i] = i
v2 = sin(v1);
v3 = 2.0*(v1 + v2);
double arr[20];
copy(&v1[0],&v1[20],arr);
v1.resize(100);
FOR(i,100) v1[i] = 1.0/(1.0+i);
v1.apply(sqrt);                 // same as v1 = sqrt(v1)

valarray is convenient because you can use it in mathematical expressions, such as v1+v2 or sin(v1) in the preceding code. These operations are shortcuts that apply to every element in the arrays. In fact, there is a method called apply() that applies a function to each element in turn. In the preceding code, v1.apply(sqrt) does the same as v1=sqrt(v1), but is more efficient because the second expression involves a temporary array that is then copied. As with strings, you should be aware that temporary creation takes place in valarray expressions, and this can make them rather slower than explicit loops, so it's better to use *= than *, and so on.

valarray is only efficient for larger numbers of elements. An example where valarray would not be a good solution is using them as three-dimensional vectors in a graphics program that manipulates a lot of vectors. In this case it would be worthwhile writing your own Point class.

valarray is an old-fashioned class that predates the Standard Template Library (STL) and is even considered a bit of a dinosaur in some circles. The valarray class does not cooperate well with the standard algorithms because valarray does not have the usual container interfaces, such as begin() and end(). However, in the previous example, you could still use copy() to move v1 into the array arr.

valarray has a resize() member function, but valarray is not a true resizable array because resize() does not preserve the old contents by copying. Be careful with the following constructors:

valarray ();         // these are
valarray (int sz);   // like vector!
valarray (T value, int sz);   // value comes before size!

Despite its deficiencies, valarray is designed to be fast, and it can be very useful when you need to crunch numbers.

You can access subsets of valarray by indexing it with slice, which specifies a sequence. The slice_array type behaves like valarray, but it really is a special alias that refers to an original valarray and hence cannot be copied directly. Because no copying is involved, it is very efficient. The following example prints out the odd elements of v1. This is done by indexing with a slice that begins at index 1, which has 10 elements and skips 2 elements each time. So slice(0,10,2) would refer to the even elements of v1:

FOR(i,20) v1[i] = i;   // 0,1,2,. ..
slice_array<double> v_odd = v1[slice(1,10,2)];
FOR(i,10) cout << v_odd[i] << ' ';  // 1.0, 3.0, 4.0 . . .

There are two other ways to extract subarrays. The first is to use a mask array, and the second is to use an indexing array. This example program shows how both of these methods can be used to extract a selected set of the values. A mask array is a valarray<bool>, which can be generated from a boolean expression; in this case, v1 > 0. I have dumped out the elements of this mask array, and you can see that it is true whenever v1 is positive. That is, mask[i] is true if v1[i] > 0. Using this mask to index v1 gives a valarray<double>, which only contains the positive values of v1. The second method of extracting values is to set up an indexing array, which must be of type valarray<size_t>. The result has the same size as the index and contains the specified values:


// test-valarray.cpp
#include <iostream>
#include <valarray>
using namespace std;

template <class T>
  void dump(const valarray<T>& v) {
   for(int i = 0; i < v.size(); i++)
     cout << v[i] << ' ';
   cout << endl;
  }

typedef valarray<double> DV;

int main()
{
 DV v1(15);
 int i;
 for(i = 0; i < 15; i++) v1[i] = sin(i/2);
 valarray<bool> mask = v1 > 0.0;
 dump(mask);

 DV gt_zero = v1[mask];                  // index with a mask

// gt_zero now contains all elements of v1 which are > 0;
// Calculate the sum of v1[1]+v1[4]+v1[10]
 valarray<size_t> index(3);
 index[0] = 1;
 index[1] = 4;
 index[2] = 10;
 DV subset = gt_zero[index];           // index with an index array;
 dump(subset);
 cout << "sum of these 3 elements " << subset.sum() << endl;
}

Here is the output of this program:


0 0 1 1 1 1 1 1 0 0 0
0.841471 0.14112 0
sum of these 3 elements 0.982591

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.27.72