21.5 Differences in Treatment of Zero Elements

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