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

Commit de79733

Browse files
Lint expressions of the form x.log(b) / y.log(b)
1 parent 9520d3d commit de79733

File tree

3 files changed

+190
-13
lines changed

3 files changed

+190
-13
lines changed

clippy_lints/src/floating_point_arithmetic.rs

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,29 +64,31 @@ declare_clippy_lint! {
6464

6565
declare_lint_pass!(FloatingPointArithmetic => [FLOATING_POINT_IMPROVEMENTS]);
6666

67-
fn check_log_base(cx: &LateContext<'_, '_>, expr: &Expr, args: &HirVec<Expr>) {
68-
let arg = sugg::Sugg::hir(cx, &args[0], "..").maybe_par();
69-
70-
if let Some((value, _)) = constant(cx, cx.tables, &args[1]) {
71-
let method;
72-
67+
// Returns the specialized log method for a given base if base is constant
68+
// and is one of 2, 10 and e
69+
fn get_specialized_log_method(cx: &LateContext<'_, '_>, base: &Expr) -> Option<&'static str> {
70+
if let Some((value, _)) = constant(cx, cx.tables, base) {
7371
if F32(2.0) == value || F64(2.0) == value {
74-
method = "log2";
72+
return Some("log2");
7573
} else if F32(10.0) == value || F64(10.0) == value {
76-
method = "log10";
74+
return Some("log10");
7775
} else if F32(f32_consts::E) == value || F64(f64_consts::E) == value {
78-
method = "ln";
79-
} else {
80-
return;
76+
return Some("ln");
8177
}
78+
}
79+
80+
None
81+
}
8282

83+
fn check_log_base(cx: &LateContext<'_, '_>, expr: &Expr, args: &HirVec<Expr>) {
84+
if let Some(method) = get_specialized_log_method(cx, &args[1]) {
8385
span_lint_and_sugg(
8486
cx,
8587
FLOATING_POINT_IMPROVEMENTS,
8688
expr.span,
8789
"logarithm for bases 2, 10 and e can be computed more accurately",
8890
"consider using",
89-
format!("{}.{}()", arg, method),
91+
format!("{}.{}()", sugg::Sugg::hir(cx, &args[0], ".."), method),
9092
Applicability::MachineApplicable,
9193
);
9294
}
@@ -232,6 +234,82 @@ fn check_expm1(cx: &LateContext<'_, '_>, expr: &Expr) {
232234
}
233235
}
234236

237+
// Checks whether two expressions evaluate to the same value
238+
fn are_exprs_equivalent(cx: &LateContext<'_, '_>, left: &Expr, right: &Expr) -> bool {
239+
// Checks whether the values are constant and equal
240+
if_chain! {
241+
if let Some((left_value, _)) = constant(cx, cx.tables, left);
242+
if let Some((right_value, _)) = constant(cx, cx.tables, right);
243+
if left_value == right_value;
244+
then {
245+
return true;
246+
}
247+
}
248+
249+
// Checks whether the expressions resolve to the same variable
250+
if_chain! {
251+
if let ExprKind::Path(ref left_qpath) = left.kind;
252+
if let QPath::Resolved(_, ref left_path) = *left_qpath;
253+
if left_path.segments.len() == 1;
254+
if let def::Res::Local(left_local_id) = qpath_res(cx, left_qpath, left.hir_id);
255+
if let ExprKind::Path(ref right_qpath) = right.kind;
256+
if let QPath::Resolved(_, ref right_path) = *right_qpath;
257+
if right_path.segments.len() == 1;
258+
if let def::Res::Local(right_local_id) = qpath_res(cx, right_qpath, right.hir_id);
259+
if left_local_id == right_local_id;
260+
then {
261+
return true;
262+
}
263+
}
264+
265+
false
266+
}
267+
268+
fn check_log_division(cx: &LateContext<'_, '_>, expr: &Expr) {
269+
let log_methods = ["log", "log2", "log10", "ln"];
270+
271+
if_chain! {
272+
if let ExprKind::Binary(op, ref lhs, ref rhs) = expr.kind;
273+
if op.node == BinOpKind::Div;
274+
if cx.tables.expr_ty(lhs).is_floating_point();
275+
if let ExprKind::MethodCall(left_path, _, left_args) = &lhs.kind;
276+
if let ExprKind::MethodCall(right_path, _, right_args) = &rhs.kind;
277+
let left_method = left_path.ident.name.as_str();
278+
if left_method == right_path.ident.name.as_str();
279+
if log_methods.iter().any(|&method| left_method == method);
280+
then {
281+
let left_recv = &left_args[0];
282+
let right_recv = &right_args[0];
283+
284+
// Return early when bases are not equal
285+
if left_method == "log" && !are_exprs_equivalent(cx, &left_args[1], &right_args[1]) {
286+
return;
287+
}
288+
289+
// Reduce the expression further for bases 2, 10 and e
290+
let suggestion = if let Some(method) = get_specialized_log_method(cx, right_recv) {
291+
format!("{}.{}()", sugg::Sugg::hir(cx, left_recv, ".."), method)
292+
} else {
293+
format!(
294+
"{}.log({})",
295+
sugg::Sugg::hir(cx, left_recv, ".."),
296+
sugg::Sugg::hir(cx, right_recv, "..")
297+
)
298+
};
299+
300+
span_lint_and_sugg(
301+
cx,
302+
FLOATING_POINT_IMPROVEMENTS,
303+
expr.span,
304+
"x.log(b) / y.log(b) can be reduced to x.log(y)",
305+
"consider using",
306+
suggestion,
307+
Applicability::MachineApplicable,
308+
);
309+
}
310+
}
311+
}
312+
235313
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatingPointArithmetic {
236314
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
237315
if let ExprKind::MethodCall(ref path, _, args) = &expr.kind {
@@ -247,6 +325,7 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FloatingPointArithmetic {
247325
}
248326
} else {
249327
check_expm1(cx, expr);
328+
check_log_division(cx, expr);
250329
}
251330
}
252331
}

tests/ui/floating_point_arithmetic.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,30 @@ fn check_expm1() {
8282
let _ = x.exp() - 1.0 * 2.0;
8383
}
8484

85+
fn check_log_division() {
86+
let x = 3f32;
87+
let y = 2f32;
88+
let b = 4f32;
89+
90+
let _ = x.log2() / y.log2();
91+
let _ = x.log10() / y.log10();
92+
let _ = x.ln() / y.ln();
93+
let _ = x.log(4.0) / y.log(4.0);
94+
let _ = x.log(b) / y.log(b);
95+
let _ = x.log(b) / y.log(x);
96+
let _ = x.log(b) / 2f32.log(b);
97+
98+
let x = 3f64;
99+
let y = 2f64;
100+
let b = 4f64;
101+
102+
let _ = x.log2() / y.log2();
103+
let _ = x.log10() / y.log10();
104+
let _ = x.ln() / y.ln();
105+
let _ = x.log(4.0) / y.log(4.0);
106+
let _ = x.log(b) / y.log(b);
107+
let _ = x.log(b) / y.log(x);
108+
let _ = x.log(b) / 2f64.log(b);
109+
}
110+
85111
fn main() {}

tests/ui/floating_point_arithmetic.stderr

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,5 +192,77 @@ error: (e.pow(x) - 1) can be computed more accurately
192192
LL | let _ = x.exp() - 1.0 + 2.0;
193193
| ^^^^^^^^^^^^^ help: consider using: `x.exp_m1()`
194194

195-
error: aborting due to 32 previous errors
195+
error: x.log(b) / y.log(b) can be reduced to x.log(y)
196+
--> $DIR/floating_point_arithmetic.rs:90:13
197+
|
198+
LL | let _ = x.log2() / y.log2();
199+
| ^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
200+
201+
error: x.log(b) / y.log(b) can be reduced to x.log(y)
202+
--> $DIR/floating_point_arithmetic.rs:91:13
203+
|
204+
LL | let _ = x.log10() / y.log10();
205+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
206+
207+
error: x.log(b) / y.log(b) can be reduced to x.log(y)
208+
--> $DIR/floating_point_arithmetic.rs:92:13
209+
|
210+
LL | let _ = x.ln() / y.ln();
211+
| ^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
212+
213+
error: x.log(b) / y.log(b) can be reduced to x.log(y)
214+
--> $DIR/floating_point_arithmetic.rs:93:13
215+
|
216+
LL | let _ = x.log(4.0) / y.log(4.0);
217+
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
218+
219+
error: x.log(b) / y.log(b) can be reduced to x.log(y)
220+
--> $DIR/floating_point_arithmetic.rs:94:13
221+
|
222+
LL | let _ = x.log(b) / y.log(b);
223+
| ^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
224+
225+
error: x.log(b) / y.log(b) can be reduced to x.log(y)
226+
--> $DIR/floating_point_arithmetic.rs:96:13
227+
|
228+
LL | let _ = x.log(b) / 2f32.log(b);
229+
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log2()`
230+
231+
error: x.log(b) / y.log(b) can be reduced to x.log(y)
232+
--> $DIR/floating_point_arithmetic.rs:102:13
233+
|
234+
LL | let _ = x.log2() / y.log2();
235+
| ^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
236+
237+
error: x.log(b) / y.log(b) can be reduced to x.log(y)
238+
--> $DIR/floating_point_arithmetic.rs:103:13
239+
|
240+
LL | let _ = x.log10() / y.log10();
241+
| ^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
242+
243+
error: x.log(b) / y.log(b) can be reduced to x.log(y)
244+
--> $DIR/floating_point_arithmetic.rs:104:13
245+
|
246+
LL | let _ = x.ln() / y.ln();
247+
| ^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
248+
249+
error: x.log(b) / y.log(b) can be reduced to x.log(y)
250+
--> $DIR/floating_point_arithmetic.rs:105:13
251+
|
252+
LL | let _ = x.log(4.0) / y.log(4.0);
253+
| ^^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
254+
255+
error: x.log(b) / y.log(b) can be reduced to x.log(y)
256+
--> $DIR/floating_point_arithmetic.rs:106:13
257+
|
258+
LL | let _ = x.log(b) / y.log(b);
259+
| ^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log(y)`
260+
261+
error: x.log(b) / y.log(b) can be reduced to x.log(y)
262+
--> $DIR/floating_point_arithmetic.rs:108:13
263+
|
264+
LL | let _ = x.log(b) / 2f64.log(b);
265+
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider using: `x.log2()`
266+
267+
error: aborting due to 44 previous errors
196268

0 commit comments

Comments
 (0)