Two unrelated questions about NaN and iterators

Do you know why f64::sqrt returns NaN when the input is sub zero instead of returning an Option<f64>?

And it's a good idea to add functions f64::sqrt_opt(), f64::asin_opt() and f64::acos_opt() (and similar for f32) to stdlib that return Option<f64> or Option<f32> (and never NaN, even when their input is a NaN)?


Iterator operations like Iterator::eq are quite handy:

fn main() {
    let it1 = [0_u32, 1, 4, 9, 16].iter().cloned();
    let it2 = (0_u32 ..= 4).map(|i| i.pow(2));
    println!("{}", it1.eq(it2));
}

To do the same thing in Python you need more complex code:

from itertools import zip_longest
it1 = iter([0, 1, 4, 9, 16])
it2 = (i ** 2 for i in range(5))
print(all(x == y for x,y in zip_longest(it1, it2)))

But do you know why does Rust stdlib uses eq() instead of == to test such equality?

fn main() {
    let it1 = [0_u32, 1, 4, 9, 16].iter().cloned();
    let it2 = (0_u32 ..= 4).map(|i| i.pow(2));
    println!("{}", it1 == it2); // Not supported.
}

f64::sqrt returns NaN for negative integers, because its what processors do. Checking for NaN and return option for every floating point calculation would cause serious overhead, which is not acceptable for system language. Also it would make expressions more complicated to write (you will need '?' not only for functions call - almost every operation on fp may result in NaN, in particular division may cause it even if both arguments are finite). Also if you want to prevent NaN, you also want to care about Inf, and Inf could be caused even by addition of two finite fp. If checking for invalid cases is crucial in your code (which isn't so rare), just look at f64::is_finite.

According to iterators - because implementing Iterator doesn't assumes, that also PartialEq is defined. Obviously you could say, that its reasonable, that if something is iterator, PartialEq should be implemented by default to just forward to Iterator::eq, but what if user would redefine PartialEq to better match his iterator type? In such case, if you will use ==, the redefined PartialEq would be called instead of comparing item by item. Making long story short - == is not part of Iterator interface, its part of PartialEq interface which is orthogonal to Iterator.

2 Likes

Right. (I have suggested separated functions like f64::sqrt_opt() usable when out of band errors are preferred and performance is less important).

Why you want to have Option result instead of NaN? For now, you may just call f64::is_finite to check if value is finite (and provide recovery path or default value). Calling additional calculations by mapping on option is neither convinient nor fast. If you really need Option (eg some of your functions takes Option), there is boolinator crate, which allows you to do this easly (wrap value into option basing on bool). If anything like sqrt_opt or so one, maybe function like f64::finite which returns Some for finite values, and None, for NaN/Inf, but I don't consider it useful - however I may be wrong, just create RFC if you like this idea.

Also you may check this crate: numeric_float::n32f - Rust.

There are several crates that provide non-nan float wrappers, and the problem with putting one of them in std is that there are many different ways to do it which serve many different purposes. Floating point numbers were engineered to handle a huge variety of computational scenarios, so it wouldn't help many people to bless a narrow-band library that makes assumptions for only one domain.

Because Rust's functions follow the IEEE spec. And, more specifically, because that proposed output type doesn't guarantee that it's not Some(NAN).

Because iterator equality is different from whether the same items are produced. Imagine a console input iterator for example. And you probably want 1.. == 1.. to be true in O(1), not for it to panic after O(n).

2 Likes