Calculating the average or mean is a familiar way of summarizing a set of numbers, but its result has little meaning in some contexts. For example, mains electricity, the power delivered to residences and most businesses, is an alternating current, varying between positive and negative values. Averaging the voltage over an even number of cycles is always zero. Yet, touching the wires (do I need to say, "Don't try this?") suggests a non-zero voltage. The root mean square (RMS) is a computed value that provides a more meaningful interpretation of the data in situations like this. (Worldwide voltages vary, with 220 to 240 volts the most common, followed by 120 volts in North America.) The text uses the RMS calculation to further illustrate arrays and the Σ operator.
Translating the root mean square to C++.
The discrete version of the RMS formula illustrates additional information sometimes included with the summation operator: the lower and upper bounds of the summation. The lower bound is located below or at the bottom right of the Σ and introduces an index variable; the upper bound is located above or at the top right of the Σ.
Two equivalent versions of the RMS formula: The first version uses the Σ or summation operator with upper and lower bounds. The second version replaces Σ with an indeterminate number of terms, illustrating how the index variable identifies each term.
A picture illustrating how the information encoded with the Σ operator relates to a C++ for-loop. Starting in the center of the formula and working outwards further helps to connect the formula elements to the implementing C++ expressions:
Get one data value
xi → x[i]
Square the value
xi2 → pow(x[i], 2)
Sum the squares
Σ xi2 → sum += pow(x[i], 2)
Loop through all data values
\(\sum_{i=0}^{n-1} {x_i}^2\) → for (i = 0; i < n; ...)
Divide the sum by n and take the square root
\(\sqrt{{1 \over n} \sum_{i=0}^{n-1} {x_i}^2}\) → for (int i = 0; i < n; i++) sum += pow(x[i], 2);
Formulas including upper and lower bounds with the Σ operator, use the index variable in two ways:
\(\sum_{i=0}^{n-1} {x_i}^2\) The index variable only identifies the summation terms
\(\sum_{i=0}^{n-1} i^2\) The index variable identifies the summation terms, and the term value also depends on the variable
The RMS Function
array, root mean square, RMS
The previous figure effectively translates the RMS formula into C++. All that remains is to add a function header, an accumulator variable, and a return statement.
//double rms(double x[], int n) // (a)
double rms(double* x, int n)
{
double sum = 0; // (b)
for (int i = 0; i < n; i++) // (c)
sum += pow(x[i], 2);
return sqrt(sum / n); // (d)
}
The C++ RMS function.
It's conceivable that a statistical, scientific, or engineering program may need to calculate many RMS values; it's equally conceivable that many programs may need to solve an RMS problem. Both cases imply the value of implementing the RMS calculation as a function.
Two versions of the function's header, illustrating two ways of defining the array parameter, x. The function doesn't limit the array's size or length, but the client program must indicate how many elements the array contains, n.
sum accumulates the terms as the for-loop calculates them.
The for-loop uses the Σ operator's bounds to iterate over the calculated terms.
The final statement calculates and returns the final RMS value by dividing the sum by n and computing the square root of the quotient.
A complete program requires a driver to provide input and display the output. The following figures demonstrate three options, comparing their respective advantages and disadvantages.
int main()
{
int n = 0;
cout << "Number of data points?: "; // (i)
cin >> n;
if (n < 1) exit(1);
double* x = new double[n]; // (ii)
for (int i = 0; i < n; i++) // (iii)
{
cout << i << ": ";
cin >> x[i];
}
cout << "rms = " << rms(x, n) << endl; // (iv)
delete x; // (v)
return 0;
}
int main()
{
int n = 0; // (i)
double x[1000];
while (!cin.eof()) // (ii)
cin >> x[n++];
n--; // (iii)
//for (cin >> x[n]; !cin.eof(); cin >> x[++n])
// ;
if (n < 1) exit(1);
cout << "rms = " << rms(x, n) << endl; // (iv)
return 0;
}
(a)
(b)
Array versions of the RMS program.
Storing data in an array separates the input and calculation operations. The first version allocates the array on the heap, allowing the program to match the array's size to the data. The second version allocates the array on the stack. The size of stack arrays must be specified as a compile-time constant, forcing the program to create a large array. The array's fixed size limits the amount of data it can store, while wasting space when processes smaller amounts of data. Programs must explicitly deallocate heap memory, whereas programs automatically deallocate stack memory.
Having users enter the number of data points requires that they know the number in advance.
Prompts for and reads the number of data points.
Allocates an array of the heap to hold the data.
Reads the data from the console.
Calculates and displays the RMS of the data.
Deallocates the heap memory.
This version counts the data points as it reads them from the console, but it requires users to enter an end-of-file character to terminate the input. The specific end-of-file character depends on the hosting operating system: control-Z on Windows, and (typically but configurable) control-D on Linux and related systems. The program detects the file's end with the eof function, but only after the user has entered the end-of-file character. So, the program reads and counts the end-of-file character, requiring the program to "uncount" it.
The program initializes the data counter to 0, so it can count the data as it reads it.
The while-loop iterates, reading and counting data, until it detects the end-of-file.
Decrements the data counter, leaving the end-of-file character in the array but ignoring it. The for-loop is an alternate input technique, replacing (ii) and (iii). It's compact but challenging to understand. Try describing its behavior before looking at the answer1 below.
Calculates and displays the RMS of the data.
nt main()
{
double x; // (a)
int n = 0; // (b)
double sum = 0;
for (cin >> x; !cin.eof(); n++) // (c)
{
sum += pow(x, 2); // (d)
cin >> x; // (e)
}
cout << "rms = " << sqrt(sum / n) << endl; // (f)
return 0;
}
Non-array version of the RMS program.
In addition to making the RMS problem relevant for the current chapter, storing data in an array supports a separate, reusable RMS function. However, matching the array's size to the data is problematic. A dynamic data structure like a vector would solve the problem, but the text doesn't introduce them until Chapter 13. A program can calculate the RMS value without using an array by combining the data input and summing operations, as this example illustrates.
A variable for input and intermediate calculations.
Accumulators for counting the data and summing the terms.
The three for-loop expressions run in order, from the left to the right:
The initial read.
The loop-body runs if the last character read was not an end-of-file.
Executes after the loop-body, counting the data.
Squares the data and adds it to the sum.
Reads the next data.
Calculates and displays the RMS value: the sqrt argument is the quotient of the accumulated sum, and the counted number of data values n.
For-loops consist of three expressions and a body (enclosed with braces) that run in a well-defined order. cin >> x[n]; runs first, and only once, when the loop begins. It reads the initial data value. The loop test, !cin.eof();, runs next. If the test evaluates to true, the loop body runs next. The update expression, cin >> x[++n], runs after the loop-body. The expression indexes the data array, x, with the pre-increment operator, ++n, counting the previous data and advancing the index to the next array element. The loop continues running while the test expression evaluates to true, cycling through the test, the body, and the update, in that order. Notice that the test detects the end-of-file character, ending the loop, before the update increments the count.