Consider the two cases below:
// Case 1
double val { initial_value };
for (int i { 0 }; i < n; ++i) {
val += step;
foo(val);
}
// Case 2
for (int i { 0 }; i < n; ++i) {
double val = initial_value + i * step;
foo(val);
}
where n
is the number of steps, initial_value
is some given value of type double
, step
is some predetermined value of type double
and val
is a variable used in subsequent call for the function foo
. Which of the cases produces less of floating-point error? My guess would be the second one, as there are only one addition and multiplication, while the first case incurs the floating-point representation error from all of the n
additions. I am asking this question because I didn't know what to search for. Does there exists some good reference for cases like these?
In practice the variable val
is to be used in the loop of the both cases. I didn't include any example for this as I'm only interested in the floating-point error.
An integer multiplication always needs a "carry propagate add" step at the end. Consequently, addition is always faster because that's the final step of a multiplication. (Floating point is a little different, but not significantly so).
In computer systems, integer arithmetic is exact, but the possible range of values is limited. Integer arithmetic is generally faster than floating-point arithmetic. Floating-point numbers represent what were called in school “real” numbers (i.e., those that have a fractional part, such as 3.1415927).
Yes, according to http://www.agner.org/optimize/instruction_tables.pdf, add instruction is 3 times faster than mult instruction.
Multiplication is faster than division.
Considering the comment by supercat (emphasis mine):
The point is that in many scenarios one might want a sequence of values that are uniformly spaced between specified start and end points. Using the second approach would yield values that are as uniformly spaced as possible between the start point and an end value that's near a desired one, but may not quite match.
And the one by Bathsheba:
Both are flawed. You should compute the start and end, then compute each value as a function of those. The problem with the second way is you multiply the error in step. The former accumulates errors.
I'd suggest a couple of alternatives.
Since C++20, the Standard Library provides std::lerp where std::lerp(a, b, t)
returns "the linear interpolation between a and b for the parameter t (or extrapolation, when t is outside the range [0,1])".
A formula like value = (a * (n - i) + b * i) / n;
may result in a more uniform1 distribution of the intermediate values.
(1) Here I tried to test all those approaches for different extremes and number of sample points. The program compares the values generated by each algorithm when applied in the opposite directions (first from left to right, then from right to left). It shows the average and variance of the sum of the absolute difference between the values of the intermediate points.
Other metrics may yield different results.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With