【问题标题】:Rounding a f64 to nearest i64 in Rust在 Rust 中将 f64 舍入到最接近的 i64
【发布时间】:2017-04-29 13:07:43
【问题描述】:

Rust 的f64 类型提供了函数round(),它舍入到最接近的整数,但它返回一个f64。另一方面,Java 的 Math.round(double) 返回一个 long。我可以调用round(),然后转换为i64,但这能保证我得到正确的结果吗?在这里,“正确”意味着获得最接近的 i64 — Java 的 round() 返回“最接近的长”。

【问题讨论】:

  • 如果f64 是整数但超出i64 的范围,您希望发生什么?
  • @ChrisEmerson 在这种情况下,Java 似乎将 1e100 舍入到 9223372036854775807

标签: floating-point rust precision


【解决方案1】:

the book 开始,从浮点到整数类型的转换向零舍入,因此首先舍入几乎是正确的:f.round() as i64

但是,如果f64 超出i64 的范围(巨大的幅度),这也是当前未定义的行为(但这是a bug)。因此,您应该首先钳位该值(或者可能更好,引发错误或断言)。可能显而易见的答案不起作用:

f.max(std::i64::MIN as f64).min(std::i64::MAX as f64).round() as i64

因为i64::MAXf64 的转换不准确,并且将上述内容应用于1e100 最终会得到一个很大的负值(在我的测试中;如前所述,它实际上是未定义的)。

如果浮点值超出应用程序期望的合理范围,最好的选择似乎是返回一些错误。

【讨论】:

  • 看来您需要一种方法来获取适合f64 的整数值的连续范围的下限和上限,以便能够钳制或引发异常。否则,由于未定义的行为,您无法实际检查转换。也许语言应该暴露those
  • 我同意,在一般情况下。但可能有一个限制低于对任何(好的,大多数)给定应用程序有意义的限制。
  • 鉴于我们谈论的是 253,是的,可能有一个很好的限制!
【解决方案2】:

您可以为此使用conv crate:

use conv::prelude::*;

let x = 9_223_371_487_098_961_920i64 as f64;
println!("{:?}", x.approx_as_by::<i64, RoundToNearest>());
// Ok(9223371487098962944)

let x = 9_223_372_036_854_775_807i64 as f64;
println!("{:?}", x.approx_as_by::<i64, RoundToNearest>());
// Err(FloatError::PosOverflow(..))

【讨论】:

    【解决方案3】:

    这是一个简单的“封底”实现:

    const INTEGRAL_LIMIT: f64 = 9007199254740992.0;
    
    #[derive(Debug, PartialEq, Eq)]
    enum Error {
        NaN,
        Overflow,
        Underflow,
    }
    
    fn try_from(f: f64) -> Result<i64, Error> {
        let f = f.round();
    
        if f.is_nan() { return Err(Error::NaN); }
    
        if f < -INTEGRAL_LIMIT { return Err(Error::Underflow); }
        if f > INTEGRAL_LIMIT { return Err(Error::Overflow); }
    
        Ok(f as i64)
    }
    

    它带有一个通过的最小测试套件:

    fn main() {
        assert_eq!(try_from(std::f64::NAN), Err(Error::NaN));
    
        assert_eq!(try_from(std::f64::NEG_INFINITY), Err(Error::Underflow));
        assert_eq!(try_from(-9007199254740994.0), Err(Error::Underflow));
    
        assert_eq!(try_from( 9007199254740994.0), Err(Error::Overflow));
        assert_eq!(try_from(std::f64::INFINITY), Err(Error::Overflow));
    
        assert_eq!(try_from(-INTEGRAL_LIMIT), Ok(-9007199254740992));
        assert_eq!(try_from( INTEGRAL_LIMIT), Ok( 9007199254740992));
    }
    

    我实际上期待有一个可用的 TryFrom 实现,但没有找到。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多