Есть ли способ `f64 :: from (0.23_f32)` и получить 0.23_f64? - PullRequest
1 голос
/ 30 сентября 2019

Я пытаюсь связать вместе две части программного обеспечения: одну, которая дает мне f32, и другую, которая ожидает f64 значения. В моем коде я использую f64::from(my_f32), но в своем тесте я сравниваю результат, и сравниваемое значение не было преобразовано должным образом: значение f64 имеет ряд дополнительных, более точных цифр,так что значения не равны.

В моем случае это значение 0.23. Есть ли способ преобразовать 0.23_f32 в f64 так, чтобы я в итоге получил 0.23_f64 вместо 0.23000000417232513?

fn main() {
    let x = 0.23_f32;
    println!("{}", x);
    println!("{}", f64::from(x));
    println!("---");

    let x = 0.23_f64;
    println!("{}", x);
    println!("{}", f64::from(x));
}

Playground


Редактировать: Я понимаю, что числа с плавающей запятой хранятся по-разному - на самом деле, я иногда использую этот удобный визуализатор для просмотра различий в представлениях между 32-битными и 64-битными числами с плавающей запятой. Я искал, есть ли какой-нибудь умный способ обойти это.


Редактировать 2: "умный" пример, который я только что придумал, будет my_32.to_string().parse::<f64>() - который заставляет меня 0.23_f64, но (очевидно) требует разбора строки. Я хотел бы подумать, что может быть что-то, по крайней мере, немного больше связанных с числами (из-за отсутствия лучшего термина).

Ответы [ 2 ]

1 голос
/ 01 октября 2019

Комментарии уже указали, почему это происходит. Этот ответ существует, чтобы дать вам способы обойти это.

Первое (и наиболее очевидное) - это использовать библиотеки произвольной точности. Твердый пример этого в ржавчине rug. Это позволяет вам выразить практически любое число точно, но это вызывает некоторые проблемы за пределами границ FFI (среди других случаев).

Второй - делать то, что большинство людей делает с числами с плавающей запятой, и заключать в скобки ваши равенства. Поскольку вы знаете, что большинство чисел с плавающей точкой не будут сохранены точно, и вы знаете свой тип ввода, вы можете использовать константы, такие как std::f32::MIN, чтобы заключить в скобки ваш тип, например так ( plays ):

use std::cmp::PartialOrd;
use std::ops::{Add, Div, Sub};
fn bracketed_eq<
    I,
    E: From<I> + From<f32> + Clone + PartialOrd + Div<Output = E> + Sub<Output = E> + Add<Output = E>,
>(
    input: E,
    target: I,
    value: I,
) -> bool {
    let target: E = target.into();
    let value: E = value.into();
    let bracket_lhs: E = target.clone() - (value.clone() / (2.0).into());
    let bracket_rhs: E = target.clone() + (value.clone() / (2.0).into());
    bracket_lhs >= input && bracket_rhs <= input
}

#[test]
fn test() {
    let u: f32 = 0.23_f32;
    assert!(bracketed_eq(f64::from(u), 0.23, std::f32::MIN))
}

Большая часть этого является шаблонной, и многое из этого полностью оптимизируется компилятором;также можно отказаться от требования Clone, ограничив выбор некоторых черт. Add, Sub, Div предназначены для операций, From<I> для реализации преобразования, From<f32> для константы 2.0.

0 голосов
/ 01 октября 2019

Правильный способ сравнения значений с плавающей запятой - заключить их в скобки. Вопрос в том, как определить интервал брекетинга? В вашем случае, поскольку у вас есть представление целевого значения как f32, у вас есть два решения:

  • Очевидное решение состоит в том, чтобы сделать сравнение между f32 с, поэтомупреобразуйте результат f64 в f32, чтобы избавиться от лишних цифр, и сравните его с ожидаемым результатом. Конечно, это все равно может не сработать, если из-за накопленных ошибок округления результат будет немного отличаться.

  • Правильным решением было бы использование функции next_afterчтобы получить наименьший интервал брекетинга вокруг вашей цели:

    let result: f64 = 0.23f64;
    let expect: f32 = 0.23;
    
    assert_ne!(result, expect.into());
    assert!(expect.next_after (0.0).into() < result && result < expect.next_after (1.0).into());
    

    , но, к сожалению, это никогда не стабилизировалось (см. # 27752 ).

  • Таквам нужно будет определить приемлемую для вас точность, возможно, как функцию f32::EPSILON:

    let result: f64 = 0.23f64;
    let expect: f32 = 0.23;
    
    assert_ne!(result, expect.into());
    assert!(f64::from (expect) - f64::from (std::f32::EPSILON) < result && result < f64::from (expect) + f64::from (std::f32::EPSILON);
    

Если вы неЕсли вы не хотите сравнивать значение, но вместо этого хотите обрезать его перед передачей в какое-то вычисление, тогда нужно использовать следующую функцию: f64::round:

const PRECISION: f64 = 100.0;
let from_db: f32 = 0.23;
let truncated = (f64::from (from_db) * PRECISION).round() / PRECISION;
println!("f32   : {:.32}", from_db);
println!("f64   : {:.32}", 0.23f64);
println!("output: {:.32}", truncated);

prints:

f32   : 0.23000000417232513427734375000000
f64   : 0.23000000000000000999200722162641
output: 0.23000000000000000999200722162641

Пара примечаний:

  • Результат по-прежнему не равен 0,23, поскольку это число не может быть представлено как f64 (или как f32 в этом отношении), но это как можно ближе.
  • Если есть юридические последствия, как вы предполагали, ттогда вам, вероятно, не следует использовать числа с плавающей запятой в первую очередь, но вы должны использовать либо некую фиксированную точку с юридически установленной точностью, либо некоторую библиотеку произвольной точности.
...