I need to implement a for
loop that goes from one floating point number to another with the step as another floating point number.
I know how to implement that in C-like languages:
for (float i = -1.0; i < 1.0; i += 0.01) { /* ... */ }
I also know that in Rust I can specify the loop step using step_by
, and that gives me what I want if I have the boundary values and step as integers:
#![feature(iterator_step_by)]
fn main() {
for i in (0..30).step_by(3) {
println!("Index {}", i);
}
}
When I do that with floating point numbers, it results in a compilation error:
#![feature(iterator_step_by)]
fn main() {
for i in (-1.0..1.0).step_by(0.01) {
println!("Index {}", i);
}
}
And here is the compilation output:
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`
How can I implement this loop in Rust?
All simple fractions exactly. All decimals precisely, even when the decimals can be represented in a small number of digits. All digits of large values, meaning that incrementing a large floating-point value might not change that value within the available precision.
Integer to Float To convert an integer to a float in Rust, use as f64 . This is a useful means to convert from various interchangeable types, you can use inline also.
Another answer using iterators but in a slightly different way 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);
}
}
Under nightly you can use the conservative impl trait
feature to avoid the Vec
allocation 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);
}
}
If you haven't yet, I invite you to read Goldberg's What Every Computer Scientist Should Know About Floating-Point Arithmetic.
The problem with floating points is that your code may be doing 200 or 201 iterations, depending on whether the last step of the loop ends up being i = 0.99
or i = 0.999999
(which is still < 1
even if really close).
To avoid this footgun, Rust does not allow iterating over a range of f32
or f64
. Instead, it forces you to use integral steps:
for i in -100i8..100 {
let i = f32::from(i) * 0.01;
// ...
}
See also:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With