Making diagonal and permutation matrices special matrix objects in their own right and the consequent usage of smarter algorithms for certain operations implies, as a side effect, small differences in treating zeros. The contents of this section apply also to sparse matrices, discussed in the following chapter. (see Sparse Matrices)
The IEEE 754 floating point standard defines the result of the expressions
0*Inf and 0*NaN as NaN. This is widely agreed to be a
good compromise and Octave strives to conform to this standard. However,
certain algorithms designed for maximized efficiency with structured and sparse
matrices may not conform to the IEEE 754 floating point standard regarding
operations with zero elements that should result in NaN such as
0*Inf, 0/0, or 0*NaN.
Numerical software dealing with structured and sparse matrices (including Octave) almost always makes a distinction between a "numerical zero" and an "assumed zero". A "numerical zero" is a zero value occurring in a place where any floating-point value could occur. It is normally stored somewhere in memory as an explicit value. An "assumed zero", on the contrary, is a zero matrix element implied by the matrix structure (diagonal, triangular) or a sparsity pattern; its value is usually not stored explicitly anywhere, but is implied by the underlying data structure.
The primary distinction is that an assumed zero, when multiplied or divided by
any number, yields always a zero, even when, e.g., multiplied by
Inf or divided by NaN. The reason for this behavior is that the
numerical multiplication is not actually performed anywhere by the underlying
algorithm; the result is just assumed to be zero.
Octave mitigates this behavior by branching the underlying algorithm into specialized implementations to handle operations that actually affect assumed zeros. This is primarily a trade-off between computational efficiency and conformity to the floating point standard by treating assumed zeros as numerical zeros. Besides the difference in computation speed, this behavior also affects sparsity patterns that should be taken into account when operating on very large sparse matrices which may result in out-of-memory errors.
Examples of treating assumed zeros as numerical zeros in sparse matrices:
speye (3) ⇒ Compressed Column Sparse (rows = 3, cols = 3, nnz = 3 [33%]) (1, 1) -> 1 (2, 2) -> 1 (3, 3) -> 1 Inf * speye (3) ⇒ Compressed Column Sparse (rows = 3, cols = 3, nnz = 9 [100%]) (1, 1) -> Inf (2, 1) -> NaN (3, 1) -> NaN (1, 2) -> NaN (2, 2) -> Inf (3, 2) -> NaN (1, 3) -> NaN (2, 3) -> NaN (3, 3) -> Inf Inf * full (eye (3)) ⇒ Inf NaN NaN NaN Inf NaN NaN NaN Inf speye (3) / 0 ⇒ Compressed Column Sparse (rows = 3, cols = 3, nnz = 9 [100%]) (1, 1) -> Inf (2, 1) -> NaN (3, 1) -> NaN (1, 2) -> NaN (2, 2) -> Inf (3, 2) -> NaN (1, 3) -> NaN (2, 3) -> NaN (3, 3) -> Inf full (eye (3)) / 0 ⇒ Inf NaN NaN NaN Inf NaN NaN NaN Inf
Although in sparse matrices only the sparsity pattern may be affected by certain operations, when dealing with diagonal matrices, such operations may also affect the returned type. The following rules apply:
For example:
3 * eye (3)
⇒
Diagonal Matrix
3 0 0
0 3 0
0 0 3
Inf * eye (3)
⇒
Inf NaN NaN
NaN Inf NaN
NaN NaN Inf
eye (3) / 0
⇒
Inf NaN NaN
NaN Inf NaN
NaN NaN Inf
eye (3) / NaN
⇒
NaN NaN NaN
NaN NaN NaN
NaN NaN NaN
eye (3) / Inf
⇒
Diagonal Matrix
0 0 0
0 0 0
0 0 0
eye (3) / 2
⇒
Diagonal Matrix
0.5000 0 0
0 0.5000 0
0 0 0.5000
Note that Octave (as well as MATLAB) does not strictly conform to the IEEE 754 floating point standard and there are certain cases like in the following examples where the underlying algorithms operate under assumed zeros without treating then as numerical zeros. As of Octave 11, the intention is to make Octave fully compliant with the IEEE 754 standard so that operations on dense, sparse, or any other special matrix types produce the same consistent results. Thus, it is expected that the output in the following examples may change in future Octave versions.
Examples of effects of assumed zeros vs. numerical zeros:
diag (1:3) * [NaN; 1; 1]
⇒
NaN
2
3
sparse (1:3,1:3,1:3) * [NaN; 1; 1]
⇒
NaN
2
3
[1,0,0;0,2,0;0,0,3] * [NaN; 1; 1]
⇒
NaN
NaN
NaN