Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit c636c6a

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 fc5d0cc commit c636c6a

File tree

6 files changed

+489
-0
lines changed

6 files changed

+489
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,7 @@ Released 2018-09-13
11691169
[`ifs_same_cond`]: https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond
11701170
[`implicit_hasher`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_hasher
11711171
[`implicit_return`]: https://rust-lang.github.io/rust-clippy/master/index.html#implicit_return
1172+
[`inaccurate_floating_point_computation`]: https://rust-lang.github.io/rust-clippy/master/index.html#inaccurate_floating_point_computation
11721173
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
11731174
[`indexing_slicing`]: https://rust-lang.github.io/rust-clippy/master/index.html#indexing_slicing
11741175
[`ineffective_bit_mask`]: https://rust-lang.github.io/rust-clippy/master/index.html#ineffective_bit_mask
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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10001000
store.register_late_pass(|| box to_digit_is_some::ToDigitIsSome);
10011001
let array_size_threshold = conf.array_size_threshold;
10021002
store.register_late_pass(move || box large_stack_arrays::LargeStackArrays::new(array_size_threshold));
1003+
store.register_late_pass(move || box floating_point_arithmetic::FloatingPointArithmetic);
10031004
store.register_early_pass(|| box as_conversions::AsConversions);
10041005
store.register_early_pass(|| box utils::internal_lints::ProduceIce);
10051006
store.register_late_pass(|| box let_underscore::LetUnderscore);
@@ -1648,6 +1649,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
16481649
store.register_group(true, "clippy::nursery", Some("clippy_nursery"), vec![
16491650
LintId::of(&attrs::EMPTY_LINE_AFTER_OUTER_ATTR),
16501651
LintId::of(&fallible_impl_from::FALLIBLE_IMPL_FROM),
1652+
LintId::of(&floating_point_arithmetic::INACCURATE_FLOATING_POINT_COMPUTATION),
1653+
LintId::of(&floating_point_arithmetic::SLOW_FLOATING_POINT_COMPUTATION),
16511654
LintId::of(&missing_const_for_fn::MISSING_CONST_FOR_FN),
16521655
LintId::of(&mul_add::MANUAL_MUL_ADD),
16531656
LintId::of(&mutable_debug_assertion::DEBUG_ASSERT_WITH_MUT_CALL),

src/lintlist/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,13 @@ pub const ALL_LINTS: [Lint; 357] = [
749749
deprecation: None,
750750
module: "implicit_return",
751751
},
752+
Lint {
753+
name: "inaccurate_floating_point_computation",
754+
group: "nursery",
755+
desc: "checks for numerically unstable floating point computations",
756+
deprecation: None,
757+
module: "floating_point_arithmetic",
758+
},
752759
Lint {
753760
name: "inconsistent_digit_grouping",
754761
group: "style",

tests/ui/floating_point_arithmetic.rs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#![allow(dead_code)]
2+
#![warn(
3+
clippy::inaccurate_floating_point_computation,
4+
clippy::slow_floating_point_computation
5+
)]
6+
7+
const TWO: f32 = 2.0;
8+
const E: f32 = std::f32::consts::E;
9+
10+
fn check_log_base() {
11+
let x = 1f32;
12+
let _ = x.log(2f32);
13+
let _ = x.log(10f32);
14+
let _ = x.log(std::f32::consts::E);
15+
let _ = x.log(TWO);
16+
let _ = x.log(E);
17+
18+
let x = 1f64;
19+
let _ = x.log(2f64);
20+
let _ = x.log(10f64);
21+
let _ = x.log(std::f64::consts::E);
22+
}
23+
24+
fn check_ln1p() {
25+
let x = 1f32;
26+
let _ = (1.0 + x).ln();
27+
let _ = (1.0 + x * 2.0).ln();
28+
let _ = (1.0 + x.powi(2)).ln();
29+
let _ = (1.0 + x.powi(2) * 2.0).ln();
30+
let _ = (1.0 + (std::f32::consts::E - 1.0)).ln();
31+
// Cases where the lint shouldn't be applied
32+
let _ = (x + 1.0).ln();
33+
let _ = (1.0 + x + 2.0).ln();
34+
let _ = (1.0 + x - 2.0).ln();
35+
36+
let x = 1f64;
37+
let _ = (1.0 + x).ln();
38+
let _ = (1.0 + x * 2.0).ln();
39+
let _ = (1.0 + x.powi(2)).ln();
40+
// Cases where the lint shouldn't be applied
41+
let _ = (x + 1.0).ln();
42+
let _ = (1.0 + x + 2.0).ln();
43+
let _ = (1.0 + x - 2.0).ln();
44+
}
45+
46+
fn check_powf() {
47+
let x = 3f32;
48+
let _ = 2f32.powf(x);
49+
let _ = std::f32::consts::E.powf(x);
50+
let _ = x.powf(1.0 / 2.0);
51+
let _ = x.powf(1.0 / 3.0);
52+
53+
let x = 3f64;
54+
let _ = 2f64.powf(x);
55+
let _ = std::f64::consts::E.powf(x);
56+
let _ = x.powf(1.0 / 2.0);
57+
let _ = x.powf(1.0 / 3.0);
58+
}
59+
60+
fn check_expm1() {
61+
let x = 2f32;
62+
let _ = x.exp() - 1.0;
63+
let _ = x.exp() - 1.0 + 2.0;
64+
// Cases where the lint shouldn't be applied
65+
let _ = x.exp() - 2.0;
66+
let _ = x.exp() - 1.0 * 2.0;
67+
68+
let x = 2f64;
69+
let _ = x.exp() - 1.0;
70+
let _ = x.exp() - 1.0 + 2.0;
71+
// Cases where the lint shouldn't be applied
72+
let _ = x.exp() - 2.0;
73+
let _ = x.exp() - 1.0 * 2.0;
74+
}
75+
76+
fn main() {}

0 commit comments

Comments
 (0)