Getting Nan value in safe code

This code is complied without warnings:


fn main() {
    let mut _v : Vec<f64> = vec![];


    let mut count : f64 = 0.;
    let _median : f64 = {
        let mut sum :f64 = 0.;
        for i in &_v {
            sum += *i;
            count += 1.;
            println!("never call");
        }
        sum
    } / count;

    println!("Median={:#?}", _median);
}

After have run getting:


    Finished dev [unoptimized + debuginfo] target(s) in 0.33s
     Running `target/debug/mean_median_mode`
Median=NaN

So there is situation of the failed safe code.

Creating the value NaN is not considered unsafe. Rust does not prevent all bugs.

1 Like

NaN is a valid floating point special value.

1 Like

It looks like the defect in Rust compiler.
Because there is an announcement that Rust compiler should catch the situation
of uninitialized variable.
So if Nan is valid why there is that valid value in uninitialized variable?

NaN is not the same as the value being uninitialized. The value NaN is an actual specific floating point value like 2.0, 7.5 or infinity.

5 Likes

If you want an example of an uninitialized value, here you go:

fn main() {
    let mut val: f64;
    
    if false {
        val = 10.0;
    }
    
    println!("{}", val);
}
error[E0381]: borrow of possibly-uninitialized variable: `val`
 --> src/main.rs:9:20
  |
9 |     println!("{}", val);
  |                    ^^^ use of possibly-uninitialized `val`

To fix this, you could initialize val to some default value. For example, you could use NaN for this:

fn main() {
    let mut val = f64::NAN;
    
    if false {
        val = 10.0;
    }
    
    println!("{}", val);
}
NaN
2 Likes

So the question why is Nan in the _median?
There is no assigning Nan to the _median.

It's because you are dividing by zero. You are doing this because when the list is empty, this results in count being zero.

1 Like

Why doesn't Rust throw an exception like in the situation of i32?

A signaling NaN in any arithmetic operation (including numerical comparisons) will cause an "invalid operation.

Most languages generate a NaN value when you divide by zero. This is not Rust being weird, if anything, it's floating point being weird.

5 Likes

I'll be reducing your example :

fn main() {
    let mut _v : Vec<f64> = vec![];
    let mut count : f64 = 0.;
    let _median : f64 = {
        let mut sum :f64 = 0.;
        for i in &_v {
            sum += *i;
            count += 1.;
            println!("never call");
        }
        sum
    } / count;
    println!("Median={:#?}", _median);
}

Removing all the noise :

fn main() {
    let mut count: f64 = 0.;
    let median: f64 = {
        let mut sum: f64 = 0.;
        sum
    } / count;
    println!("Median={:#?}", median);
}

Second pass :

fn main() {
    let median = 0_f64 / 0_f64;
    println!("Median={:#?}", median);
}

0 divided by 0 is defined to be NaN by the specs. (Incidentally that's "correct" maths, 0 divided by 0 is... nothing, rare instance of correct maths in floating point arithmetics)

Thank you.
Could you please send the link to the specs?
Actually Rust is going to be the language that helps to prevent various sort of errors.
And idea is very good!
But in the real situation (my testcase is about it) Rust doesn't help to catch an error.
It's bad.

In your case, you should use an Option<f64> in the case of an empty iterator. The behavior of floating point numbers is independant from Rust.

2 Likes

Ok. Thank you.

Unfortunately the official IEEE-754 document is under the paywall. The first version of it was published in 1985 and made few revisions during decades, but basic operations are mostly untouched.

https://standards.ieee.org/standard/754-2019.html

But there're many explanatory articles about it including this one from 30 years ago.

https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

4 Likes

There is the Python:

0/0
Traceback (most recent call last):
File "", line 1, in
ZeroDivisionError: division by zero

0./0.
Traceback (most recent call last):
File "", line 1, in
ZeroDivisionError: float division by zero

Interestingly, popular maths library NumPy returns a NaN instead:

import numpy as np

print(np.float64(0)/0)  # will print out 'nan'

I tried a couple languages out and Java, JavaScript, C#, Ruby and C++ are other examples of languages where 0.0/0.0 = NaN. PHP throws a DivisionByZeroError like Python.

2 Likes

In most CPUs except some low-end microcontrollers IEEE754 defined operations are implemented directly using silicon gates called FPU.

To do so the CPython manually check if the dividend is zero and throw exception which may impact performance. But it's the way how it's defined on the python.

Checking for division with floating point integers is a lot more expensive than just producing NaN because NaN is what you get from the assembly instruction, and a panic would involve a branch on every division.

3 Likes

Numpy prints warning:

import numpy as np

print(np.float64(0)/0)
:1: RuntimeWarning: invalid value encountered in double_scalars
nan