Skip to content

Commit

Permalink
Add lints to detect inaccurate and inefficient FP operations
Browse files Browse the repository at this point in the history
Add lints to detect floating point computations that are either
inaccurate or inefficient and suggest better alternatives.
  • Loading branch information
krishna-veerareddy committed Dec 14, 2019
1 parent d82debb commit 42b9e14
Show file tree
Hide file tree
Showing 7 changed files with 502 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,7 @@ Released 2018-09-13
[`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond
[`implicit_hasher`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_hasher
[`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return
[`inaccurate_floating_point_computation`]: https://rust-lang.github.io/rust-clippy/master/index.html#inaccurate_floating_point_computation
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
Expand Down Expand Up @@ -1192,6 +1193,7 @@ Released 2018-09-13
[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
[`slow_floating_point_computation`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_floating_point_computation
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string
[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.

[There are 340 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
[There are 342 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)

We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:

Expand Down
228 changes: 228 additions & 0 deletions clippy_lints/src/floating_point_arithmetic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
use crate::consts::{
constant,
Constant::{F32, F64},
};
use crate::utils::*;
use if_chain::if_chain;
use rustc::declare_lint_pass;
use rustc::hir::*;
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
use rustc_errors::Applicability;
use rustc_session::declare_tool_lint;
use std::f32::consts as f32_consts;
use std::f64::consts as f64_consts;

declare_clippy_lint! {
/// **What it does:** Looks for numerically unstable floating point
/// computations and suggests better alternatives.
///
/// **Why is this bad?** Numerically unstable floating point computations
/// cause rounding errors to magnify and distorts the results strongly.
///
/// **Known problems:** None
///
/// **Example:**
///
/// ```rust
/// use std::f32::consts::E;
///
/// let a = 1f32.log(2.0);
/// let b = 1f32.log(10.0);
/// let c = 1f32.log(E);
/// ```
///
/// is better expressed as
///
/// ```rust
/// let a = 1f32.log2();
/// let b = 1f32.log10();
/// let c = 1f32.ln();
/// ```
pub INACCURATE_FLOATING_POINT_COMPUTATION,
nursery,
"checks for numerically unstable floating point computations"
}

declare_clippy_lint! {
/// **What it does:** Looks for inefficient floating point computations
/// and suggests faster alternatives.
///
/// **Why is this bad?** Lower performance.
///
/// **Known problems:** None
///
/// **Example:**
///
/// ```rust
/// use std::f32::consts::E;
///
/// let a = (2f32).powf(3.0);
/// let c = E.powf(3.0);
/// ```
///
/// is better expressed as
///
/// ```rust
/// let a = (3f32).exp2();
/// let b = (3f32).exp();
/// ```
pub SLOW_FLOATING_POINT_COMPUTATION,
nursery,
"checks for inefficient floating point computations"
}

declare_lint_pass!(FloatingPointArithmetic => [
INACCURATE_FLOATING_POINT_COMPUTATION,
SLOW_FLOATING_POINT_COMPUTATION
]);

fn check_log_base(cx: &LateContext<'_, '_>, expr: &Expr, args: &HirVec<Expr>) {
let recv = &args[0];
let arg = sugg::Sugg::hir(cx, recv, "..").maybe_par();

if let Some((value, _)) = constant(cx, cx.tables, &args[1]) {
let method;

if F32(2.0) == value || F64(2.0) == value {
method = "log2";
} else if F32(10.0) == value || F64(10.0) == value {
method = "log10";
} else if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
method = "ln";
} else {
return;
}

span_lint_and_sugg(
cx,
INACCURATE_FLOATING_POINT_COMPUTATION,
expr.span,
"logarithm for bases 2, 10 and e can be computed more accurately",
"consider using",
format!("{}.{}()", arg, method),
Applicability::MachineApplicable,
);
}
}

// TODO: Lint expressions of the form `(x + 1).ln()` and `(x + y).ln()`
// where y > 1 and suggest usage of `(x + (y - 1)).ln_1p()` instead
fn check_ln1p(cx: &LateContext<'_, '_>, expr: &Expr, args: &HirVec<Expr>) {
if_chain! {
if let ExprKind::Binary(op, ref lhs, ref rhs) = &args[0].kind;
if op.node == BinOpKind::Add;
if let Some((value, _)) = constant(cx, cx.tables, lhs);
if F32(1.0) == value || F64(1.0) == value;
then {
let arg = sugg::Sugg::hir(cx, rhs, "..").maybe_par();

span_lint_and_sugg(
cx,
INACCURATE_FLOATING_POINT_COMPUTATION,
expr.span,
"ln(1 + x) can be computed more accurately",
"consider using",
format!("{}.ln_1p()", arg),
Applicability::MachineApplicable,
);
}
}
}

fn check_powf(cx: &LateContext<'_, '_>, expr: &Expr, args: &HirVec<Expr>) {
// Check receiver
if let Some((value, _)) = constant(cx, cx.tables, &args[0]) {
let method;

if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
method = "exp";
} else if F32(2.0) == value || F64(2.0) == value {
method = "exp2";
} else {
return;
}

span_lint_and_sugg(
cx,
SLOW_FLOATING_POINT_COMPUTATION,
expr.span,
"exponent for bases 2 and e can be computed more efficiently",
"consider using",
format!("{}.{}()", sugg::Sugg::hir(cx, &args[1], "..").maybe_par(), method),
Applicability::MachineApplicable,
);
}

// Check argument
if let Some((value, _)) = constant(cx, cx.tables, &args[1]) {
let help;
let method;

if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value {
help = "square-root of a number can be computer more efficiently";
method = "sqrt";
} else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value {
help = "cube-root of a number can be computer more efficiently";
method = "cbrt";
} else {
return;
}

span_lint_and_sugg(
cx,
SLOW_FLOATING_POINT_COMPUTATION,
expr.span,
help,
"consider using",
format!("{}.{}()", sugg::Sugg::hir(cx, &args[0], ".."), method),
Applicability::MachineApplicable,
);
}
}

// TODO: Lint expressions of the form `x.exp() - y` where y > 1
// and suggest usage of `x.exp_m1() - (y - 1)` instead
fn check_expm1(cx: &LateContext<'_, '_>, expr: &Expr) {
if_chain! {
if let ExprKind::Binary(op, ref lhs, ref rhs) = expr.kind;
if op.node == BinOpKind::Sub;
if cx.tables.expr_ty(lhs).is_floating_point();
if let Some((value, _)) = constant(cx, cx.tables, rhs);
if F32(1.0) == value || F64(1.0) == value;
if let ExprKind::MethodCall(ref path, _, ref method_args) = lhs.kind;
if path.ident.name.as_str() == "exp";
then {
span_lint_and_sugg(
cx,
INACCURATE_FLOATING_POINT_COMPUTATION,
expr.span,
"(e.pow(x) - 1) can be computed more accurately",
"consider using",
format!(
"{}.exp_m1()",
sugg::Sugg::hir(cx, &method_args[0], "..")
),
Applicability::MachineApplicable,
);
}
}
}

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatingPointArithmetic {
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
if let ExprKind::MethodCall(ref path, _, args) = &expr.kind {
let recv_ty = cx.tables.expr_ty(&args[0]);

if recv_ty.is_floating_point() {
match &*path.ident.name.as_str() {
"ln" => check_ln1p(cx, expr, args),
"log" => check_log_base(cx, expr, args),
"powf" => check_powf(cx, expr, args),
_ => {},
}
}
} else {
check_expm1(cx, expr);
}
}
}
6 changes: 6 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ pub mod excessive_precision;
pub mod exit;
pub mod explicit_write;
pub mod fallible_impl_from;
pub mod floating_point_arithmetic;
pub mod format;
pub mod formatting;
pub mod functions;
Expand Down Expand Up @@ -520,6 +521,8 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
&exit::EXIT,
&explicit_write::EXPLICIT_WRITE,
&fallible_impl_from::FALLIBLE_IMPL_FROM,
&floating_point_arithmetic::INACCURATE_FLOATING_POINT_COMPUTATION,
&floating_point_arithmetic::SLOW_FLOATING_POINT_COMPUTATION,
&format::USELESS_FORMAT,
&formatting::POSSIBLE_MISSING_COMMA,
&formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
Expand Down Expand Up @@ -968,6 +971,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
store.register_late_pass(|| box to_digit_is_some::ToDigitIsSome);
let array_size_threshold = conf.array_size_threshold;
store.register_late_pass(move || box large_stack_arrays::LargeStackArrays::new(array_size_threshold));
store.register_late_pass(move || box floating_point_arithmetic::FloatingPointArithmetic);
store.register_early_pass(|| box as_conversions::AsConversions);
store.register_early_pass(|| box utils::internal_lints::ProduceIce);

Expand Down Expand Up @@ -1582,6 +1586,8 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
LintId::of(&attrs::EMPTY_LINE_AFTER_OUTER_ATTR),
LintId::of(&fallible_impl_from::FALLIBLE_IMPL_FROM),
LintId::of(&floating_point_arithmetic::INACCURATE_FLOATING_POINT_COMPUTATION),
LintId::of(&floating_point_arithmetic::SLOW_FLOATING_POINT_COMPUTATION),
LintId::of(&missing_const_for_fn::MISSING_CONST_FOR_FN),
LintId::of(&mul_add::MANUAL_MUL_ADD),
LintId::of(&mutex_atomic::MUTEX_INTEGER),
Expand Down
16 changes: 15 additions & 1 deletion src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub use lint::Lint;
pub use lint::LINT_LEVELS;

// begin lint list, do not remove this comment, it’s used in `update_lints`
pub const ALL_LINTS: [Lint; 340] = [
pub const ALL_LINTS: [Lint; 342] = [
Lint {
name: "absurd_extreme_comparisons",
group: "correctness",
Expand Down Expand Up @@ -735,6 +735,13 @@ pub const ALL_LINTS: [Lint; 340] = [
deprecation: None,
module: "implicit_return",
},
Lint {
name: "inaccurate_floating_point_computation",
group: "nursery",
desc: "checks for numerically unstable floating point computations",
deprecation: None,
module: "floating_point_arithmetic",
},
Lint {
name: "inconsistent_digit_grouping",
group: "style",
Expand Down Expand Up @@ -1813,6 +1820,13 @@ pub const ALL_LINTS: [Lint; 340] = [
deprecation: None,
module: "matches",
},
Lint {
name: "slow_floating_point_computation",
group: "nursery",
desc: "checks for inefficient floating point computations",
deprecation: None,
module: "floating_point_arithmetic",
},
Lint {
name: "slow_vector_initialization",
group: "perf",
Expand Down
Loading

0 comments on commit 42b9e14

Please sign in to comment.