Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Alternatives to matching floating point ranges

Tags:

rust

What's the best way to write a series of floating point range comparisons? To use the example from the GitHub comment below,

let color = match foo {
    0.0...0.1 => Color::Red,
    0.1...0.4 => Color::Yellow,
    0.4...0.8 => Color::Blue,
    _         => Color::Grey,
};

The naive solution would be a painful if-else chain:

let color = {
    if 0.0 <= foo && foo < 0.1 {
        Color::Red
    }
    else if 0.1 <= foo && foo < 0.4 {
        Color::Yellow
    }
    else if 0.4 <= foo && foo < 0.8 {
        Color:: Blue
    }
    else {
        Color::Grey
    }
}

Is that really the best option available? There must be a nicer way to write this, right?


Related to Alternatives to matching floating points, but this is for range comparisons.

Originally mentioned in the tracking issue for illegal_floating_point_literal_pattern, and something I'm running into constantly.

like image 792
couchand Avatar asked Feb 28 '18 19:02

couchand


2 Answers

Since Rust 1.35, the InRange functionality implemented by Boiethios is already provided in the contains method on Range<f32>:

impl From<f32> for Color {
    fn from(f: f32) -> Color {
        match f {
            x if (0.0..0.1).contains(&x) => Color::Red,
            x if (0.1..0.4).contains(&x) => Color::Yellow,
            x if (0.4..0.8).contains(&x) => Color::Blue,
            _ => Color::Grey,
        }
    }
}

However, I would tend to write this with only one comparison against each number, which reduces the chance of typos introducing bugs:

impl From<f32> for Color {
    fn from(f: f32) -> Color {
        match f {
            x if x < 0.0 => Color::Grey;
            x if x < 0.1 => Color::Red,
            x if x < 0.4 => Color::Yellow,
            x if x < 0.8 => Color::Blue,
            _ => Color::Grey,
        }
    }
}

This style also makes it more obvious that there are two disjoint ranges that return Color::Grey: x < 0.0 and x >= 0.8.

See also

  • How can I test if a value lies within a Range?
like image 119
trent Avatar answered Oct 17 '22 22:10

trent


I would personally do this:

#[derive(Debug, PartialEq)]
enum Color {
    Red,
    Yellow,
    Blue,
    Grey,
}

trait InRange {
    fn in_range(self, begin: Self, end: Self) -> bool;
}

impl InRange for f32 {
    fn in_range(self, begin: f32, end: f32) -> bool {
        self >= begin && self < end
    }
}

impl From<f32> for Color {
    fn from(f: f32) -> Color {
        match f {
            x if x.in_range(0.0, 0.1) => Color::Red,
            x if x.in_range(0.1, 0.4) => Color::Yellow,
            x if x.in_range(0.4, 0.8) => Color::Blue,
            _ => Color::Grey,
        }
    }
}

fn main() {
    assert_eq!(Color::from(0.2), Color::Yellow);
}
like image 37
Boiethios Avatar answered Oct 17 '22 22:10

Boiethios