Skip to content

Commit 42b9e14

Browse files
Add lints to detect inaccurate and inefficient FP operations
Add lints to detect floating point computations that are either inaccurate or inefficient and suggest better alternatives.
1 parent d82debb commit 42b9e14

File tree

7 files changed

+502
-2
lines changed

7 files changed

+502
-2
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,7 @@ Released 2018-09-13
10331033
[`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond
10341034
[`implicit_hasher`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_hasher
10351035
[`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return
1036+
[`inaccurate_floating_point_computation`]: https://rust-lang.github.io/rust-clippy/master/index.html#inaccurate_floating_point_computation
10361037
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
10371038
[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
10381039
[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
@@ -1192,6 +1193,7 @@ Released 2018-09-13
11921193
[`single_char_pattern`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_char_pattern
11931194
[`single_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match
11941195
[`single_match_else`]: https://rust-lang.github.io/rust-clippy/master/index.html#single_match_else
1196+
[`slow_floating_point_computation`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_floating_point_computation
11951197
[`slow_vector_initialization`]: https://rust-lang.github.io/rust-clippy/master/index.html#slow_vector_initialization
11961198
[`str_to_string`]: https://rust-lang.github.io/rust-clippy/master/index.html#str_to_string
11971199
[`string_add`]: https://rust-lang.github.io/rust-clippy/master/index.html#string_add

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

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

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

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

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
use crate::consts::{
2+
constant,
3+
Constant::{F32, F64},
4+
};
5+
use crate::utils::*;
6+
use if_chain::if_chain;
7+
use rustc::declare_lint_pass;
8+
use rustc::hir::*;
9+
use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
10+
use rustc_errors::Applicability;
11+
use rustc_session::declare_tool_lint;
12+
use std::f32::consts as f32_consts;
13+
use std::f64::consts as f64_consts;
14+
15+
declare_clippy_lint! {
16+
/// **What it does:** Looks for numerically unstable floating point
17+
/// computations and suggests better alternatives.
18+
///
19+
/// **Why is this bad?** Numerically unstable floating point computations
20+
/// cause rounding errors to magnify and distorts the results strongly.
21+
///
22+
/// **Known problems:** None
23+
///
24+
/// **Example:**
25+
///
26+
/// ```rust
27+
/// use std::f32::consts::E;
28+
///
29+
/// let a = 1f32.log(2.0);
30+
/// let b = 1f32.log(10.0);
31+
/// let c = 1f32.log(E);
32+
/// ```
33+
///
34+
/// is better expressed as
35+
///
36+
/// ```rust
37+
/// let a = 1f32.log2();
38+
/// let b = 1f32.log10();
39+
/// let c = 1f32.ln();
40+
/// ```
41+
pub INACCURATE_FLOATING_POINT_COMPUTATION,
42+
nursery,
43+
"checks for numerically unstable floating point computations"
44+
}
45+
46+
declare_clippy_lint! {
47+
/// **What it does:** Looks for inefficient floating point computations
48+
/// and suggests faster alternatives.
49+
///
50+
/// **Why is this bad?** Lower performance.
51+
///
52+
/// **Known problems:** None
53+
///
54+
/// **Example:**
55+
///
56+
/// ```rust
57+
/// use std::f32::consts::E;
58+
///
59+
/// let a = (2f32).powf(3.0);
60+
/// let c = E.powf(3.0);
61+
/// ```
62+
///
63+
/// is better expressed as
64+
///
65+
/// ```rust
66+
/// let a = (3f32).exp2();
67+
/// let b = (3f32).exp();
68+
/// ```
69+
pub SLOW_FLOATING_POINT_COMPUTATION,
70+
nursery,
71+
"checks for inefficient floating point computations"
72+
}
73+
74+
declare_lint_pass!(FloatingPointArithmetic => [
75+
INACCURATE_FLOATING_POINT_COMPUTATION,
76+
SLOW_FLOATING_POINT_COMPUTATION
77+
]);
78+
79+
fn check_log_base(cx: &LateContext<'_, '_>, expr: &Expr, args: &HirVec<Expr>) {
80+
let recv = &args[0];
81+
let arg = sugg::Sugg::hir(cx, recv, "..").maybe_par();
82+
83+
if let Some((value, _)) = constant(cx, cx.tables, &args[1]) {
84+
let method;
85+
86+
if F32(2.0) == value || F64(2.0) == value {
87+
method = "log2";
88+
} else if F32(10.0) == value || F64(10.0) == value {
89+
method = "log10";
90+
} else if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
91+
method = "ln";
92+
} else {
93+
return;
94+
}
95+
96+
span_lint_and_sugg(
97+
cx,
98+
INACCURATE_FLOATING_POINT_COMPUTATION,
99+
expr.span,
100+
"logarithm for bases 2, 10 and e can be computed more accurately",
101+
"consider using",
102+
format!("{}.{}()", arg, method),
103+
Applicability::MachineApplicable,
104+
);
105+
}
106+
}
107+
108+
// TODO: Lint expressions of the form `(x + 1).ln()` and `(x + y).ln()`
109+
// where y > 1 and suggest usage of `(x + (y - 1)).ln_1p()` instead
110+
fn check_ln1p(cx: &LateContext<'_, '_>, expr: &Expr, args: &HirVec<Expr>) {
111+
if_chain! {
112+
if let ExprKind::Binary(op, ref lhs, ref rhs) = &args[0].kind;
113+
if op.node == BinOpKind::Add;
114+
if let Some((value, _)) = constant(cx, cx.tables, lhs);
115+
if F32(1.0) == value || F64(1.0) == value;
116+
then {
117+
let arg = sugg::Sugg::hir(cx, rhs, "..").maybe_par();
118+
119+
span_lint_and_sugg(
120+
cx,
121+
INACCURATE_FLOATING_POINT_COMPUTATION,
122+
expr.span,
123+
"ln(1 + x) can be computed more accurately",
124+
"consider using",
125+
format!("{}.ln_1p()", arg),
126+
Applicability::MachineApplicable,
127+
);
128+
}
129+
}
130+
}
131+
132+
fn check_powf(cx: &LateContext<'_, '_>, expr: &Expr, args: &HirVec<Expr>) {
133+
// Check receiver
134+
if let Some((value, _)) = constant(cx, cx.tables, &args[0]) {
135+
let method;
136+
137+
if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
138+
method = "exp";
139+
} else if F32(2.0) == value || F64(2.0) == value {
140+
method = "exp2";
141+
} else {
142+
return;
143+
}
144+
145+
span_lint_and_sugg(
146+
cx,
147+
SLOW_FLOATING_POINT_COMPUTATION,
148+
expr.span,
149+
"exponent for bases 2 and e can be computed more efficiently",
150+
"consider using",
151+
format!("{}.{}()", sugg::Sugg::hir(cx, &args[1], "..").maybe_par(), method),
152+
Applicability::MachineApplicable,
153+
);
154+
}
155+
156+
// Check argument
157+
if let Some((value, _)) = constant(cx, cx.tables, &args[1]) {
158+
let help;
159+
let method;
160+
161+
if F32(1.0 / 2.0) == value || F64(1.0 / 2.0) == value {
162+
help = "square-root of a number can be computer more efficiently";
163+
method = "sqrt";
164+
} else if F32(1.0 / 3.0) == value || F64(1.0 / 3.0) == value {
165+
help = "cube-root of a number can be computer more efficiently";
166+
method = "cbrt";
167+
} else {
168+
return;
169+
}
170+
171+
span_lint_and_sugg(
172+
cx,
173+
SLOW_FLOATING_POINT_COMPUTATION,
174+
expr.span,
175+
help,
176+
"consider using",
177+
format!("{}.{}()", sugg::Sugg::hir(cx, &args[0], ".."), method),
178+
Applicability::MachineApplicable,
179+
);
180+
}
181+
}
182+
183+
// TODO: Lint expressions of the form `x.exp() - y` where y > 1
184+
// and suggest usage of `x.exp_m1() - (y - 1)` instead
185+
fn check_expm1(cx: &LateContext<'_, '_>, expr: &Expr) {
186+
if_chain! {
187+
if let ExprKind::Binary(op, ref lhs, ref rhs) = expr.kind;
188+
if op.node == BinOpKind::Sub;
189+
if cx.tables.expr_ty(lhs).is_floating_point();
190+
if let Some((value, _)) = constant(cx, cx.tables, rhs);
191+
if F32(1.0) == value || F64(1.0) == value;
192+
if let ExprKind::MethodCall(ref path, _, ref method_args) = lhs.kind;
193+
if path.ident.name.as_str() == "exp";
194+
then {
195+
span_lint_and_sugg(
196+
cx,
197+
INACCURATE_FLOATING_POINT_COMPUTATION,
198+
expr.span,
199+
"(e.pow(x) - 1) can be computed more accurately",
200+
"consider using",
201+
format!(
202+
"{}.exp_m1()",
203+
sugg::Sugg::hir(cx, &method_args[0], "..")
204+
),
205+
Applicability::MachineApplicable,
206+
);
207+
}
208+
}
209+
}
210+
211+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatingPointArithmetic {
212+
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
213+
if let ExprKind::MethodCall(ref path, _, args) = &expr.kind {
214+
let recv_ty = cx.tables.expr_ty(&args[0]);
215+
216+
if recv_ty.is_floating_point() {
217+
match &*path.ident.name.as_str() {
218+
"ln" => check_ln1p(cx, expr, args),
219+
"log" => check_log_base(cx, expr, args),
220+
"powf" => check_powf(cx, expr, args),
221+
_ => {},
222+
}
223+
}
224+
} else {
225+
check_expm1(cx, expr);
226+
}
227+
}
228+
}

clippy_lints/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ pub mod excessive_precision;
200200
pub mod exit;
201201
pub mod explicit_write;
202202
pub mod fallible_impl_from;
203+
pub mod floating_point_arithmetic;
203204
pub mod format;
204205
pub mod formatting;
205206
pub mod functions;
@@ -520,6 +521,8 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
520521
&exit::EXIT,
521522
&explicit_write::EXPLICIT_WRITE,
522523
&fallible_impl_from::FALLIBLE_IMPL_FROM,
524+
&floating_point_arithmetic::INACCURATE_FLOATING_POINT_COMPUTATION,
525+
&floating_point_arithmetic::SLOW_FLOATING_POINT_COMPUTATION,
523526
&format::USELESS_FORMAT,
524527
&formatting::POSSIBLE_MISSING_COMMA,
525528
&formatting::SUSPICIOUS_ASSIGNMENT_FORMATTING,
@@ -968,6 +971,7 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
968971
store.register_late_pass(|| box to_digit_is_some::ToDigitIsSome);
969972
let array_size_threshold = conf.array_size_threshold;
970973
store.register_late_pass(move || box large_stack_arrays::LargeStackArrays::new(array_size_threshold));
974+
store.register_late_pass(move || box floating_point_arithmetic::FloatingPointArithmetic);
971975
store.register_early_pass(|| box as_conversions::AsConversions);
972976
store.register_early_pass(|| box utils::internal_lints::ProduceIce);
973977

@@ -1582,6 +1586,8 @@ pub fn register_plugins(store: &mut lint::LintStore, sess: &Session, conf: &Conf
15821586
store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
15831587
LintId::of(&attrs::EMPTY_LINE_AFTER_OUTER_ATTR),
15841588
LintId::of(&fallible_impl_from::FALLIBLE_IMPL_FROM),
1589+
LintId::of(&floating_point_arithmetic::INACCURATE_FLOATING_POINT_COMPUTATION),
1590+
LintId::of(&floating_point_arithmetic::SLOW_FLOATING_POINT_COMPUTATION),
15851591
LintId::of(&missing_const_for_fn::MISSING_CONST_FOR_FN),
15861592
LintId::of(&mul_add::MANUAL_MUL_ADD),
15871593
LintId::of(&mutex_atomic::MUTEX_INTEGER),

src/lintlist/mod.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 340] = [
9+
pub const ALL_LINTS: [Lint; 342] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -735,6 +735,13 @@ pub const ALL_LINTS: [Lint; 340] = [
735735
deprecation: None,
736736
module: "implicit_return",
737737
},
738+
Lint {
739+
name: "inaccurate_floating_point_computation",
740+
group: "nursery",
741+
desc: "checks for numerically unstable floating point computations",
742+
deprecation: None,
743+
module: "floating_point_arithmetic",
744+
},
738745
Lint {
739746
name: "inconsistent_digit_grouping",
740747
group: "style",
@@ -1813,6 +1820,13 @@ pub const ALL_LINTS: [Lint; 340] = [
18131820
deprecation: None,
18141821
module: "matches",
18151822
},
1823+
Lint {
1824+
name: "slow_floating_point_computation",
1825+
group: "nursery",
1826+
desc: "checks for inefficient floating point computations",
1827+
deprecation: None,
1828+
module: "floating_point_arithmetic",
1829+
},
18161830
Lint {
18171831
name: "slow_vector_initialization",
18181832
group: "perf",

0 commit comments

Comments
 (0)