2017-12-18 204 views
1

我需要实现一个for循环,该循环从一个浮点数转到另一个浮点数。如何用边界值做一个'for'循环,并作为浮点值来执行?

我知道如何实现,在C类语言:

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

我也知道,在鲁斯特我可以指定使用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` 

我如何能实现这个循环中,锈?

+3

这个循环甚至不是用C是一个好主意,因为浮点不精确意味着你不知道你肯定何时会停止(这可能需要200个或201步在你的例子中)。 – interjay

+1

@interjay什么是替代呢?迭代整数并计算所需的浮点数会更好吗? – serge1peshcoff

+3

我就是这么做的。 – interjay

回答

6

如果你还没有,我请你读戈德堡的What Every Computer Scientist Should Know About Floating-Point Arithmetic

与浮点的问题,是你的代码可以做200或201次迭代,这取决于循环的最后一个步骤是否结束是i = 0.99i = 0.999999(这仍然是< 1即使真的很近)。

为了避免这个脚枪,Rust不允许迭代f32f64的范围。相反,它强迫你使用积分步骤:

for i in -100..100 { 
    let i = i as f32 * 0.01; 
    // ... 
} 
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); 
    } 
} 
1

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

在夜间,您可以使用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); 
    } 
} 
+0

在Vec中缓冲对我来说似乎是一个非常糟糕的主意。如果你不想实现一个实际的迭代器,你至少可以使用每晚的功能'conservative_impl_trait'并返回'impl Iterator '。 – Stefan

+0

我将添加夜间版本。我试图让它与稳定的实际返回类型一起工作,但无法弄清楚如何让它与捕获周围变量的闭包一起工作。 – user25064

+0

据我所知,你不能除非你封闭(或迭代器)。 – Stefan