【问题标题】:How to do a 'for' loop with boundary values and step as floating point values?如何使用边界值和步长作为浮点值进行“for”循环?
【发布时间】:2017-12-18 11:26:40
【问题描述】:

我需要实现一个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 中实现这个循环?

【问题讨论】:

  • 即使在 C 语言中,该循环也不是一个好主意,因为浮点不准确意味着您不确定它何时会停止(在您的示例中可能需要 200 或 201 步)。
  • @interjay 那么有什么替代方案呢?迭代整数然后从中计算所需的浮点数会更好吗?
  • 我就是这样做的。
  • 当然,您也可以将此类逻辑包装在您自己的迭代器中。

标签: for-loop rust


【解决方案1】:

如果你还没有,我邀请你阅读 Goldberg 的 What Every Computer Scientist Should Know About Floating-Point Arithmetic

浮点的问题在于您的代码可能会进行 200 或 201 次迭代,具体取决于循环的最后一步是 i = 0.99 还是 i = 0.999999(即使非常接近,它仍然是 &lt; 1 )。

为了避免这种枪法,Rust 不允许在 f32f64 的范围内进行迭代。相反,它会强制您使用完整的步骤:

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

另见:

【讨论】:

  • 我认为这不是脚炮。 Fp 数学仍然是确定性的,我们可以确定它将在编译时进行的迭代次数(总是 200 或 201,我不确定的唯一原因是我没有费心做数学)。在大多数使用 fp 的情况下,您不需要精确的迭代次数,您只是想做到这一点,以便在您的图表或其他东西上有定期间隔的刻度。我能看到的唯一问题是,如果您在假设一定数量的元素适合的情况下调整数组的大小,但是无论如何您都需要整数来索引该数组。
  • @JosephGarvin:我同意迭代次数是确定的,问题是它可能不是明显编译器是否可以推断它并不重要,如果(人类)读者不能......或者不自信。
  • 我想这很公平,而且我想总是存在有人会尝试使用 floor() 进行索引的风险。
  • @JosephGarvin 这可能会影响您是否在末尾包含打勾
【解决方案2】:

作为真正的迭代器:

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);
    }
}

【讨论】:

    【解决方案3】:

    另一个使用迭代器但方式略有不同的答案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);
        }
    }
    

    【讨论】:

    • Vec 中缓冲对我来说似乎是一个非常糟糕的主意。如果您不想实现实际的迭代器,您至少可以选择夜间功能 conservative_impl_trait 并返回 impl Iterator&lt;Item = T&gt;
    • 我将添加夜间版本。我试图让它与 stable 上的实际返回类型一起工作,但无法弄清楚如何让它与捕获它周围变量的闭包一起工作。
    • 据我所知,除非您将闭包(或迭代器)装箱,否则您不能。
    【解决方案4】:

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

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

    【讨论】:

      猜你喜欢
      • 2011-12-23
      • 1970-01-01
      • 2010-11-20
      • 1970-01-01
      • 1970-01-01
      • 2012-10-03
      • 2019-12-14
      • 1970-01-01
      • 2018-08-31
      相关资源
      最近更新 更多