2.6. Dangerous Range Generation

Range generation with the colon-operator ":" (see also Section 6.1.3.1.1) holds ready an unpleasant surprise that is caused by the finite precision of Scilab's floating-point numbers.

Colon expressions are used most often with integral initial_value, final_value, and stride. These are the safe uses. However, all three parameters can be decimal fractions! Not all decimal numbers have a finite representation in the binary system. For example, 0.1 has the infinite binary representation


             ____
        2#1.10011#E-4,

where the bar over the last four bits denotes infinite repetition of this bit pattern. Of course numbers with an infinite binary representation must approximated by finite binary numbers. In our example the approximation is


        2#1.10011001100110011001100110011001100110011001100110011#E-4,

for Scilab uses IEEE 754 double-precision reals, which carry a 53 bit mantissa.

For a detailed analysis of the floating-point induced problems, Example 2-6 rewrites a general colon-expression as a loop.

Example 2-6. Equivalent Representation of a Colon-Expression


v = initial_value : stride : final_value
   

translates into


v = []
if initial_value <= final_value
    x = initial_value
    while x <= final_value          -- note the comparison
        v = [v, x]
        x = x + stride              -- x accumulates rounding error
    end
end
   

The approximate binary representation can influence all three parameters initial_value, final_value, and stride. Furthermore, each addition of stride to x – as shown in Example 2-6 – can introduce a rounding error. To stay clean of the rounding errors, colon-expressions involving decimal fractions are best avoided.

A possible workaround is to generate a vector with an integral-only colon-expression, and then rescale the vector to the desired range. For example, if a vector from 1.0 to 8.0 in 0.7... (= 7/9) increments is wanted, a save expression is (9 : 7 : 72) / 9.0.

Another possible substitute for a fractional colon-expression is the built-in function linspace(initial_value, final_value [, n = 100]) that generates a vector of n evenly spaced numbers starting and including initial_value up to and including final_value. See Section 6.1.3.1.2 for a detailed discussion of this function.

The following piece of code shows that the rounding can strike in surprising ways. The vector generated with a colon expression ranges from 1.0 to 2.0 - x in increments of 0.25. Both, 1.0 and 0.25 have exact binary representations, thus they do not introduce any rounding error, neither does the repeated addition of 0.25 to 1.0. The upper end of the interval is decreased in increments of x = 2^i. Again, 2.0 and 2.0 - x have exact representations for the chosen values of i.


function e = eps_hi
    // Return the smallest positive number E for
    // which 1 + e is not equal to 1.  This equals one
    // ULP (unit least precision) for 1 <= |x| < 2.
    x = 1.0;
    while 1.0 + x ~= 1.0
        e = x;
        x = x / 2.0;
    end;
endfunction


function y = pow2(n)
    // Return 2^n, but do not rely on the built-in
    // exponentiation operator.
    y = prod(2.0 * ones(1, n))
endfunction


eps = eps_hi();
for i = 0:5
    printf("i = %d    ", i);
    disp(1.0 : 0.25 : (2.0 - eps*pow2(i)));
end

-->exec("colon.sce");
i = 0
!   1.    1.25    1.5    1.75    2. !

i = 1
!   1.    1.25    1.5    1.75    2. !

i = 2
!   1.    1.25    1.5    1.75    2. !

i = 3
!   1.    1.25    1.5    1.75 !

i = 4
!   1.    1.25    1.5    1.75 !

i = 5
!   1.    1.25    1.5    1.75 !

An upper boundary of 2.0 - 2^2*eps yields a vector of length 5, whereas 2.0 - 2^3*eps as the upper boundary gives a vector of length 4. The conclusion is that the rounding procedure, which Scilab imposes on the upper boundary, considers all numbers smaller than 2.0 by less than 4*eps as equal to 2.0. However, when asked directly, Scilab notices the difference as it should be:


-->2.0 - 4*eps == 2.0
 ans  =
  F  

Conclusion: Using colon expressions with fractional parameters is not recommended and linspace should be used instead.