rust 如何将边界值和步长作为浮点值进行"for“循环?

0qx6xfy6  于 2022-11-12  发布在  其他
关注(0)|答案(5)|浏览(231)

我需要实现一个for循环,该循环从一个浮点数到另一个浮点数,步骤为另一个浮点数。
我知道如何用类似C的语言实现它:

for (float i = -1.0; i < 1.0; i += 0.01) { /* ... */ }

我还知道,在Rust中,我可以使用step_by来指定循环步长,如果边界值和步长都是整数的话,这就给出了我想要的结果:


# ![feature(iterator_step_by)]

fn main() {
    for i in (0..30).step_by(3) {
        println!("Index {}", i);
    }
}

当我对浮点数执行此操作时,会导致编译错误:


# ![feature(iterator_step_by)]

fn main() {
    for i in (-1.0..1.0).step_by(0.01) {
        println!("Index {}", i);
    }
}

下面是编译输出:

error[E0599]: no method named `step_by` found for type `std::ops::Range<{float}>` in the current scope
--> src/main.rs:4:26
  |
4 |     for i in (-1.0..1.0).step_by(0.01) {
  |                          ^^^^^^^
  |
  = note: the method `step_by` exists but the following trait bounds were not satisfied:
          `std::ops::Range<{float}> : std::iter::Iterator`
          `&mut std::ops::Range<{float}> : std::iter::Iterator`

如何在Rust中实现这个循环?

ktca8awb

ktca8awb1#

浮点运算的问题在于,你的代码可能会进行200次或201次迭代,这取决于循环的最后一步是i = 0.99还是i = 0.999999(即使非常接近,它仍然是< 1)。
为了避免这种情况,Rust不允许在f32f64的范围内迭代,而是强制使用整数步长:

for i in -100i8..100 {
    let i = f32::from(i) * 0.01;
    // ...
}

另请参阅:

lvjbypge

lvjbypge2#

作为一个真实的的迭代器:
Playground

/// produces: [ linear_interpol(start, end, i/steps) | i <- 0..steps ]
/// (does NOT include "end")
///
/// linear_interpol(a, b, p) = (1 - p) * a + p * b
pub struct FloatIterator {
    current: u64,
    current_back: u64,
    steps: u64,
    start: f64,
    end: f64,
}

impl FloatIterator {
    pub fn new(start: f64, end: f64, steps: u64) -> Self {
        FloatIterator {
            current: 0,
            current_back: steps,
            steps: steps,
            start: start,
            end: end,
        }
    }

    /// calculates number of steps from (end - start) / step
    pub fn new_with_step(start: f64, end: f64, step: f64) -> Self {
        let steps = ((end - start) / step).abs().round() as u64;
        Self::new(start, end, steps)
    }

    pub fn length(&self) -> u64 {
        self.current_back - self.current
    }

    fn at(&self, pos: u64) -> f64 {
        let f_pos = pos as f64 / self.steps as f64;
        (1. - f_pos) * self.start + f_pos * self.end
    }

    /// panics (in debug) when len doesn't fit in usize
    fn usize_len(&self) -> usize {
        let l = self.length();
        debug_assert!(l <= ::std::usize::MAX as u64);
        l as usize
    }
}

impl Iterator for FloatIterator {
    type Item = f64;

    fn next(&mut self) -> Option<Self::Item> {
        if self.current >= self.current_back {
            return None;
        }
        let result = self.at(self.current);
        self.current += 1;
        Some(result)
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let l = self.usize_len();
        (l, Some(l))
    }

    fn count(self) -> usize {
        self.usize_len()
    }
}

impl DoubleEndedIterator for FloatIterator {
    fn next_back(&mut self) -> Option<Self::Item> {
        if self.current >= self.current_back {
            return None;
        }
        self.current_back -= 1;
        let result = self.at(self.current_back);
        Some(result)
    }
}

impl ExactSizeIterator for FloatIterator {
    fn len(&self) -> usize {
        self.usize_len()
    }

    //fn is_empty(&self) -> bool {
    //    self.length() == 0u64
    //}
}

pub fn main() {
    println!(
        "count: {}",
        FloatIterator::new_with_step(-1.0, 1.0, 0.01).count()
    );
    for f in FloatIterator::new_with_step(-1.0, 1.0, 0.01) {
        println!("{}", f);
    }
}
6tqwzwtp

6tqwzwtp3#

这基本上与the accepted answer中的操作相同,但您可能更喜欢这样写:

for i in (-100..100).map(|x| x as f64 * 0.01) {
    println!("Index {}", i);
}
pod7payv

pod7payv4#

另一个答案是使用迭代器,但方式略有不同playground

extern crate num;
use num::{Float, FromPrimitive};

fn linspace<T>(start: T, stop: T, nstep: u32) -> Vec<T>
where
    T: Float + FromPrimitive,
{
    let delta: T = (stop - start) / T::from_u32(nstep - 1).expect("out of range");
    return (0..(nstep))
        .map(|i| start + T::from_u32(i).expect("out of range") * delta)
        .collect();
}

fn main() {
    for f in linspace(-1f32, 1f32, 3) {
        println!("{}", f);
    }
}

在nightly下,您可以使用conservative impl trait功能来避免Vec分配playground


# ![feature(conservative_impl_trait)]

extern crate num;
use num::{Float, FromPrimitive};

fn linspace<T>(start: T, stop: T, nstep: u32) -> impl Iterator<Item = T>
where
    T: Float + FromPrimitive,
{
    let delta: T = (stop - start) / T::from_u32(nstep - 1).expect("out of range");
    return (0..(nstep))
        .map(move |i| start + T::from_u32(i).expect("out of range") * delta);
}

fn main() {
    for f in linspace(-1f32, 1f32, 3) {
        println!("{}", f);
    }
}
lnlaulya

lnlaulya5#

由于其他人提到的原因,在大多数情况下不应该使用float进行循环。
对于那些合适的情况,它可以做到(虽然不符合人体工程学,这可能是一个很好的设计-生 rust 应该使它更难杂耍运行链锯)。
从Rust 1.34开始,std::iter::successors()支持直接使用浮点索引进行循环:

use std::iter;

const START: f64 = -1.0;
const END: f64 = 1.0;
// Increment by 0.1 (instead of 0.01 per the question) for output brevity
const INCREMENT: f64 = 0.1;

fn main() {
    iter::successors(Some(START), |i| {
        let next = i + INCREMENT;
        (next < END).then_some(next)
    })
    .for_each(|i| println!("{i}"));
}

注意有21行输出,尽管在您的问题的示例代码中,考虑到i < 1.0(而不是i <= 1.0)的条件,可能只需要20行
这是由于输出中存在精度和/或累积舍入误差,即使源代码指定从-1.0迭代到1.0,增量正好为0.1。(请随意将START值切换为0.0或0.3,以查看不同的序列输出,* 还 * 具有精度/累积舍入误差)。
Playground example

相关问题