如何获取包含浮点数的迭代器的最小值或最大值?

How do I get the minimum or maximum value of an iterator containing floating point numbers?

我理解 why the floats don't have an implementation for Ord 但当我想偷懒并使用迭代器时,这对我没有特别帮助。

是否有解决方法或简单的方法来获取包含浮点数的迭代器的最小值/最小值/min_by?

我知道可以排序(这很慢)或将其包装在另一种类型中并实现所需的交易(这很冗长),但我希望有一些更优雅的东西。

浮点数有自己的 min and max 方法来一致地处理 NaN,因此您可以折叠迭代器:

use std::f64;

fn main() {
    let x = [2.0, 1.0, -10.0, 5.0, f64::NAN];

    let min = x.iter().fold(f64::INFINITY, |a, &b| a.min(b));
    println!("{}", min);
}

打印 -10

如果你想要不同的 NaN 处理,你可以使用 PartialOrd::partial_cmp。例如,如果您希望传播 NaN,请折叠:

use std::f64;
use std::cmp::Ordering;

fn main() {
    let x = [2.0, 1.0, -10.0, 5.0, f64::NAN];

    let min = x.iter().fold(f64::INFINITY, |a, &b| {
        match PartialOrd::partial_cmp(&a, &b) {
            None => f64::NAN,
            Some(Ordering::Less) => a,
            Some(_) => b,
        }
    });
    println!("{}", min);
}

大概是这样的?

fn main() {
    use std::cmp::Ordering;
    let mut x = [2.0, 1.0, -10.0, 5.0];
    x.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
    println!("min in x: {:?}", x);
}

我遇到的一件事是 sort_by 就地改变向量,所以你不能直接在链中使用它。

如果您知道您的数据不包含 NaN,则通过展开比较来断言这一事实​​:

fn example(x: &[f64]) -> Option<f64> {
    x.iter()
        .cloned()
        .min_by(|a, b| a.partial_cmp(b).expect("Tried to compare a NaN"))
}

如果您的数据可能有 NaN,您需要专门处理这种情况。一种解决方案是说 all 16,777,214 NaN values 彼此相等,并且总是大于或小于其他数字:

use std::cmp::Ordering;

fn example(x: &[f64]) -> Option<f64> {
    x.iter()
        .cloned()
        .min_by(|a, b| {
            // all NaNs are greater than regular numbers
            match (a.is_nan(), b.is_nan()) {
                (true, true) => Ordering::Equal,
                (true, false) => Ordering::Greater,
                (false, true) => Ordering::Less,
                _ => a.partial_cmp(b).unwrap(),
            }
        })
}

有许多可用的 crate 可用于为您提供代码所需的任何语义。


您应该使用partial_cmp(b).unwrap_or(Ordering::Equal),因为当存在 NaN 时它会提供不稳定的结果,但它会导致 reader 认为它们已被处理:

use std::cmp::Ordering;
use std::f64;

fn example(x: &[f64]) -> Option<f64> {
    x.iter()
        .cloned()
        .min_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal))
}

fn main() {
    println!("{:?}", example(&[f64::NAN, 1.0]));
    println!("{:?}", example(&[1.0, f64::NAN]));
}
Some(NaN)
Some(1.0)

A​​ built-in total-ordering 名为 .total_cmp() is available on nighty, and should be on stable within a couple months, baring any surprising issues. (The vote to stablize the feature recently passed 的浮点数比较方法。)这实现了 IEEE 754 中定义的总排序,每个可能的 f64 位值都是明确排序,包括正零和负零,以及所有可能的 NaN。请注意,有些 NaN 排序在 Infinity 之上,有些 NaN 排序在 -Infinity 以下,因此在存在 NaN 的情况下“最大值”可能会造成混淆,但它会保持一致。

Floats 仍然不会实现 Ord,因此它们不会直接排序,但样板已被缩减为一行,没有任何外部导入或恐慌的机会:

#![feature(total_cmp)]

fn main() {
    let mut a: Vec<f64> = vec![2.0, 2.5, -0.5, 1.0, 1.5];
    
    let maximum = *a.iter().max_by_key(f64::total_cmp).unwrap();
    println!("The maximum value was {maximum}.");

    a.sort_by(f64::total_cmp);
}