herbie-lint

A rustc plugin to check for numerical instability


Keywords
plugin, lint, math, float, herbie
License
MPL-2.0

Documentation

Herbie lint for Rust Build Status Crates.io License

What

This plugin can add warnings or errors to your crate when using a numerically unstable floating point expression.

Quick example of what you can get when compiling tests/compile-fail/general/test.rs:

test.rs:40:5: 40:18 warning: Numerically unstable expression, #[warn(herbie)] on by default
test.rs:40     (a/b + c) * b;
               ^~~~~~~~~~~~~
test.rs:40:5: 40:18 help: Try this
test.rs:       (c * b) + a;
test.rs:67:5: 67:23 warning: Numerically unstable expression, #[warn(herbie)] on by default
test.rs:67     (a*a + b*b).sqrt();
               ^~~~~~~~~~~~~~~~~~
test.rs:67:5: 67:23 help: Try this
test.rs:       a.hypot(b);
test.rs:155:5: 155:30 warning: Numerically unstable expression, #[warn(herbie)] on by default
test.rs:155     (a+b).sin() - (a+b).cos();
                ^~~~~~~~~~~~~~~~~~~~~~~~~
test.rs:155:5: 155:30 help: Try this
test.rs:        (b.sin() * (a.sin() + a.cos())) - ((a.cos() - a.sin()) * b.cos());

As you can see, it will report numerically unstable expressions, and suggest a (sometimes over-parenthesized) more stable correction.

Usage

Plugin

This is a rustc plugin, to use it, you need a nightly Rust.

You need a database of possible corrections for this plugin to work. The database format is the same as Herbie GHC Plugin (for Haskell) from which this plugin is inspired so this file should work. Just put it in the same directory you call cargo or rustc from.

Add in your Cargo.toml:

[dependencies]
herbie-lint = "{{VERSION}}"

and in your crate:

#![feature(plugin)]
#![plugin(herbie_lint)]

See clippy's Usage section if you want to know more and if you want more Rust lints.

Configuration

If you don't want the plugin to lint a particular function or method, you can mark it with the #[herbie_ignore] attribute:

fn foo(a: f64, b: f64, c: f64) -> f64 {
    (a/b + c) * b
    // This will suggest to use “(c * b) + a” instead.
}

#[herbie_ignore]
fn bar(a: f64, b: f64, c: f64) -> f64 {
    (a/b + c) * b
    // This won't.
}

You can also put a Herbie.toml file next to your Cargo.toml with the following fields:

# Path to the database.
db_path = "Herbie.db"

# The seed used by Herbie. If not provided, a fixed seed will be used. Fixing
# the seed ensures deterministic builds.
herbie_seed = "#(1461197085 2376054483 1553562171 1611329376 2497620867 2308122621)"

# Allow the plugin to call Herbie on unknown expressions. Positive results from
# Herbie will be cached in the database. WARNING: Herbie is slow.
# If ‘true’, the plugin will fail if it cannot find the executable.
# If ‘false’, the plugin will not try to run Herbie.
# By default, the plugin will call the executable only if it's found, but won't
# complain otherwise.
use_herbie = false

# Maximum time in seconds that Herbie is allowed to play with an expression. If
# null, allow Herbie to run indefinitely. Default is two minutes.
timeout = 120

More information about calling Herbie can be found in the wiki.

Acknowledgment

Thanks to @llogiq for the idea.