2016-12-14 3 views
5

Тип Rust's f64 предоставляет функцию round(), которая округляется до ближайшего целого числа, но возвращает f64. Java Math.round(double), с другой стороны, возвращает long. Я могу позвонить round(), а затем отправить на i64, но это гарантирует, что я получу правильный результат? Здесь «правильный» означает получение ближайшего i64 - Java round() возвращает «ближайший длинный».Округление f64 до ближайшего i64 в Rust

+4

Что вы хотите, если 'f64' является интегральным, но выходит за пределы диапазона' i64'? –

+0

@ChrisEmerson в этом случае Java кажется круглым '1e100' до' 9223372036854775807'. – Shepmaster

ответ

5

От the book конверсии с плавающей точкой на целые типы округлены к нулю, поэтому округление сначала почти правильно: f.round() as i64.

Однако в настоящее время это неопределенное поведение (но это a bug), если f64 находится за пределами допустимого диапазона (огромная величина) i64. Поэтому сначала нужно закрепить значение (или, возможно, лучше, поднять ошибку или утвердить). , Возможно, очевидный ответ не работает:

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

потому, что превращения i64::MAX в f64 не точны, и применяя выше 1e100 заканчивается с большим отрицательным значением (в моем тесте, как уже упоминалось, это на самом деле не определено).

Лучшим вариантом является возвращение некоторой ошибки, если значение с плавающей запятой выходит за пределы разумного диапазона, ожидаемого вашим приложением.

+0

Кажется, вам нужен способ получить нижний и верхний пределы смежного диапазона интегральных значений, который вписывается в 'f64', чтобы иметь возможность зажимать или создавать исключение. В противном случае из-за неопределенного поведения вы не сможете проверить конверсию. Возможно, язык должен раскрывать [те] (http://stackoverflow.com/questions/3793838/which-is-the-first-integer-that-an-ieee-754-float-is-incapable-of-representing-e). –

+0

Согласен, в общем случае. Но, вероятно, есть предел ниже тех, что имеет смысл для любого (нормально, самого) данного приложения. –

+0

Учитывая, что речь идет о 2 , да, есть, вероятно, хороший предел! –

5

Вы можете использовать conv клети для этого:

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(..)) 
1

Вот простой «на обратной стороне конверта» реализация:

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 implementa чтобы быть доступным, но не нашел ни одного.

Смежные вопросы