1 use clippy_utils
::consts
::{
2 constant
, constant_simple
, Constant
,
3 Constant
::{Int, F32, F64}
,
5 use clippy_utils
::diagnostics
::span_lint_and_sugg
;
6 use clippy_utils
::higher
;
7 use clippy_utils
::{eq_expr_value, get_parent_expr, in_constant, numeric_literal, peel_blocks, sugg}
;
8 use if_chain
::if_chain
;
9 use rustc_errors
::Applicability
;
10 use rustc_hir
::{BinOpKind, Expr, ExprKind, PathSegment, UnOp}
;
11 use rustc_lint
::{LateContext, LateLintPass}
;
13 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
14 use rustc_span
::source_map
::Spanned
;
17 use std
::f32::consts
as f32_consts
;
18 use std
::f64::consts
as f64_consts
;
21 declare_clippy_lint
! {
23 /// Looks for floating-point expressions that
24 /// can be expressed using built-in methods to improve accuracy
25 /// at the cost of performance.
27 /// ### Why is this bad?
28 /// Negatively impacts accuracy.
33 /// let _ = a.powf(1.0 / 3.0);
34 /// let _ = (1.0 + a).ln();
35 /// let _ = a.exp() - 1.0;
42 /// let _ = a.ln_1p();
43 /// let _ = a.exp_m1();
45 #[clippy::version = "1.43.0"]
48 "usage of imprecise floating point operations"
51 declare_clippy_lint
! {
53 /// Looks for floating-point expressions that
54 /// can be expressed using built-in methods to improve both
55 /// accuracy and performance.
57 /// ### Why is this bad?
58 /// Negatively impacts accuracy and performance.
62 /// use std::f32::consts::E;
65 /// let _ = (2f32).powf(a);
66 /// let _ = E.powf(a);
67 /// let _ = a.powf(1.0 / 2.0);
68 /// let _ = a.log(2.0);
69 /// let _ = a.log(10.0);
71 /// let _ = a.powf(2.0);
72 /// let _ = a * 2.0 + 4.0;
73 /// let _ = if a < 0.0 {
78 /// let _ = if a < 0.0 {
85 /// is better expressed as
88 /// use std::f32::consts::E;
95 /// let _ = a.log10();
97 /// let _ = a.powi(2);
98 /// let _ = a.mul_add(2.0, 4.0);
100 /// let _ = -a.abs();
102 #[clippy::version = "1.43.0"]
103 pub SUBOPTIMAL_FLOPS
,
105 "usage of sub-optimal floating point operations"
108 declare_lint_pass
!(FloatingPointArithmetic
=> [
113 // Returns the specialized log method for a given base if base is constant
114 // and is one of 2, 10 and e
115 fn get_specialized_log_method(cx
: &LateContext
<'_
>, base
: &Expr
<'_
>) -> Option
<&'
static str> {
116 if let Some((value
, _
)) = constant(cx
, cx
.typeck_results(), base
) {
117 if F32(2.0) == value
|| F64(2.0) == value
{
119 } else if F32(10.0) == value
|| F64(10.0) == value
{
120 return Some("log10");
121 } else if F32(f32_consts
::E
) == value
|| F64(f64_consts
::E
) == value
{
129 // Adds type suffixes and parenthesis to method receivers if necessary
130 fn prepare_receiver_sugg
<'a
>(cx
: &LateContext
<'_
>, mut expr
: &'a Expr
<'a
>) -> Sugg
<'a
> {
131 let mut suggestion
= Sugg
::hir(cx
, expr
, "..");
133 if let ExprKind
::Unary(UnOp
::Neg
, inner_expr
) = &expr
.kind
{
138 // if the expression is a float literal and it is unsuffixed then
139 // add a suffix so the suggestion is valid and unambiguous
140 if let ty
::Float(float_ty
) = cx
.typeck_results().expr_ty(expr
).kind();
141 if let ExprKind
::Lit(lit
) = &expr
.kind
;
142 if let ast
::LitKind
::Float(sym
, ast
::LitFloatType
::Unsuffixed
) = lit
.node
;
147 // Check for float literals without numbers following the decimal
148 // separator such as `2.` and adds a trailing zero
149 if sym
.as_str().ends_with('
.'
) {
157 suggestion
= match suggestion
{
158 Sugg
::MaybeParen(_
) => Sugg
::MaybeParen(op
),
159 _
=> Sugg
::NonParen(op
)
164 suggestion
.maybe_par()
167 fn check_log_base(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>, receiver
: &Expr
<'_
>, args
: &[Expr
<'_
>]) {
168 if let Some(method
) = get_specialized_log_method(cx
, &args
[0]) {
173 "logarithm for bases 2, 10 and e can be computed more accurately",
175 format
!("{}.{}()", Sugg
::hir(cx
, receiver
, "..").maybe_par(), method
),
176 Applicability
::MachineApplicable
,
181 // TODO: Lint expressions of the form `(x + y).ln()` where y > 1 and
182 // suggest usage of `(x + (y - 1)).ln_1p()` instead
183 fn check_ln1p(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>, receiver
: &Expr
<'_
>) {
184 if let ExprKind
::Binary(
186 node
: BinOpKind
::Add
, ..
193 constant(cx
, cx
.typeck_results(), lhs
),
194 constant(cx
, cx
.typeck_results(), rhs
),
196 (Some((value
, _
)), _
) if F32(1.0) == value
|| F64(1.0) == value
=> rhs
,
197 (_
, Some((value
, _
))) if F32(1.0) == value
|| F64(1.0) == value
=> lhs
,
205 "ln(1 + x) can be computed more accurately",
207 format
!("{}.ln_1p()", prepare_receiver_sugg(cx
, recv
)),
208 Applicability
::MachineApplicable
,
213 // Returns an integer if the float constant is a whole number and it can be
214 // converted to an integer without loss of precision. For now we only check
215 // ranges [-16777215, 16777216) for type f32 as whole number floats outside
216 // this range are lossy and ambiguous.
217 #[expect(clippy::cast_possible_truncation)]
218 fn get_integer_from_float_constant(value
: &Constant
) -> Option
<i32> {
220 F32(num
) if num
.fract() == 0.0 => {
221 if (-16_777_215.0..16_777_216.0).contains(num
) {
222 Some(num
.round() as i32)
227 F64(num
) if num
.fract() == 0.0 => {
228 if (-2_147_483_648.0..2_147_483_648.0).contains(num
) {
229 Some(num
.round() as i32)
238 fn check_powf(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>, receiver
: &Expr
<'_
>, args
: &[Expr
<'_
>]) {
240 if let Some((value
, _
)) = constant(cx
, cx
.typeck_results(), receiver
) {
241 if let Some(method
) = if F32(f32_consts
::E
) == value
|| F64(f64_consts
::E
) == value
{
243 } else if F32(2.0) == value
|| F64(2.0) == value
{
252 "exponent for bases 2 and e can be computed more accurately",
254 format
!("{}.{}()", prepare_receiver_sugg(cx
, &args
[0]), method
),
255 Applicability
::MachineApplicable
,
261 if let Some((value
, _
)) = constant(cx
, cx
.typeck_results(), &args
[0]) {
262 let (lint
, help
, suggestion
) = if F32(1.0 / 2.0) == value
|| F64(1.0 / 2.0) == value
{
265 "square-root of a number can be computed more efficiently and accurately",
266 format
!("{}.sqrt()", Sugg
::hir(cx
, receiver
, "..").maybe_par()),
268 } else if F32(1.0 / 3.0) == value
|| F64(1.0 / 3.0) == value
{
271 "cube-root of a number can be computed more accurately",
272 format
!("{}.cbrt()", Sugg
::hir(cx
, receiver
, "..").maybe_par()),
274 } else if let Some(exponent
) = get_integer_from_float_constant(&value
) {
277 "exponentiation with integer powers can be computed more efficiently",
280 Sugg
::hir(cx
, receiver
, "..").maybe_par(),
281 numeric_literal
::format(&exponent
.to_string(), None
, false)
295 Applicability
::MachineApplicable
,
300 fn check_powi(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>, receiver
: &Expr
<'_
>, args
: &[Expr
<'_
>]) {
301 if let Some((value
, _
)) = constant(cx
, cx
.typeck_results(), &args
[0]) {
303 if let Some(parent
) = get_parent_expr(cx
, expr
) {
304 if let Some(grandparent
) = get_parent_expr(cx
, parent
) {
305 if let ExprKind
::MethodCall(PathSegment { ident: method_name, .. }
, receiver
, ..) = grandparent
.kind
307 if method_name
.as_str() == "sqrt" && detect_hypot(cx
, receiver
).is_some() {
313 if let ExprKind
::Binary(
315 node
: BinOpKind
::Add
, ..
321 let other_addend
= if lhs
.hir_id
== expr
.hir_id { rhs }
else { lhs }
;
327 "multiply and add expressions can be calculated more efficiently and accurately",
330 "{}.mul_add({}, {})",
331 Sugg
::hir(cx
, receiver
, "..").maybe_par(),
332 Sugg
::hir(cx
, receiver
, ".."),
333 Sugg
::hir(cx
, other_addend
, ".."),
335 Applicability
::MachineApplicable
,
343 fn detect_hypot(cx
: &LateContext
<'_
>, receiver
: &Expr
<'_
>) -> Option
<String
> {
344 if let ExprKind
::Binary(
346 node
: BinOpKind
::Add
, ..
352 // check if expression of the form x * x + y * y
354 if let ExprKind
::Binary(Spanned { node: BinOpKind::Mul, .. }
, lmul_lhs
, lmul_rhs
) = add_lhs
.kind
;
355 if let ExprKind
::Binary(Spanned { node: BinOpKind::Mul, .. }
, rmul_lhs
, rmul_rhs
) = add_rhs
.kind
;
356 if eq_expr_value(cx
, lmul_lhs
, lmul_rhs
);
357 if eq_expr_value(cx
, rmul_lhs
, rmul_rhs
);
359 return Some(format
!("{}.hypot({})", Sugg
::hir(cx
, lmul_lhs
, "..").maybe_par(), Sugg
::hir(cx
, rmul_lhs
, "..")));
363 // check if expression of the form x.powi(2) + y.powi(2)
365 if let ExprKind
::MethodCall(
366 PathSegment { ident: lmethod_name, .. }
,
367 largs_0
, [largs_1
, ..],
370 if let ExprKind
::MethodCall(
371 PathSegment { ident: rmethod_name, .. }
,
372 rargs_0
, [rargs_1
, ..],
375 if lmethod_name
.as_str() == "powi" && rmethod_name
.as_str() == "powi";
376 if let Some((lvalue
, _
)) = constant(cx
, cx
.typeck_results(), largs_1
);
377 if let Some((rvalue
, _
)) = constant(cx
, cx
.typeck_results(), rargs_1
);
378 if Int(2) == lvalue
&& Int(2) == rvalue
;
380 return Some(format
!("{}.hypot({})", Sugg
::hir(cx
, largs_0
, "..").maybe_par(), Sugg
::hir(cx
, rargs_0
, "..")));
388 fn check_hypot(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>, receiver
: &Expr
<'_
>) {
389 if let Some(message
) = detect_hypot(cx
, receiver
) {
394 "hypotenuse can be computed more accurately",
397 Applicability
::MachineApplicable
,
402 // TODO: Lint expressions of the form `x.exp() - y` where y > 1
403 // and suggest usage of `x.exp_m1() - (y - 1)` instead
404 fn check_expm1(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) {
406 if let ExprKind
::Binary(Spanned { node: BinOpKind::Sub, .. }
, lhs
, rhs
) = expr
.kind
;
407 if cx
.typeck_results().expr_ty(lhs
).is_floating_point();
408 if let Some((value
, _
)) = constant(cx
, cx
.typeck_results(), rhs
);
409 if F32(1.0) == value
|| F64(1.0) == value
;
410 if let ExprKind
::MethodCall(path
, self_arg
, ..) = &lhs
.kind
;
411 if cx
.typeck_results().expr_ty(self_arg
).is_floating_point();
412 if path
.ident
.name
.as_str() == "exp";
418 "(e.pow(x) - 1) can be computed more accurately",
422 Sugg
::hir(cx
, self_arg
, "..").maybe_par()
424 Applicability
::MachineApplicable
,
430 fn is_float_mul_expr
<'a
>(cx
: &LateContext
<'_
>, expr
: &'a Expr
<'a
>) -> Option
<(&'a Expr
<'a
>, &'a Expr
<'a
>)> {
432 if let ExprKind
::Binary(Spanned { node: BinOpKind::Mul, .. }
, lhs
, rhs
) = &expr
.kind
;
433 if cx
.typeck_results().expr_ty(lhs
).is_floating_point();
434 if cx
.typeck_results().expr_ty(rhs
).is_floating_point();
436 return Some((lhs
, rhs
));
443 // TODO: Fix rust-lang/rust-clippy#4735
444 fn check_mul_add(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) {
445 if let ExprKind
::Binary(
447 node
: BinOpKind
::Add
, ..
453 if let Some(parent
) = get_parent_expr(cx
, expr
) {
454 if let ExprKind
::MethodCall(PathSegment { ident: method_name, .. }
, receiver
, ..) = parent
.kind
{
455 if method_name
.as_str() == "sqrt" && detect_hypot(cx
, receiver
).is_some() {
461 let (recv
, arg1
, arg2
) = if let Some((inner_lhs
, inner_rhs
)) = is_float_mul_expr(cx
, lhs
) {
462 (inner_lhs
, inner_rhs
, rhs
)
463 } else if let Some((inner_lhs
, inner_rhs
)) = is_float_mul_expr(cx
, rhs
) {
464 (inner_lhs
, inner_rhs
, lhs
)
473 "multiply and add expressions can be calculated more efficiently and accurately",
476 "{}.mul_add({}, {})",
477 prepare_receiver_sugg(cx
, recv
),
478 Sugg
::hir(cx
, arg1
, ".."),
479 Sugg
::hir(cx
, arg2
, ".."),
481 Applicability
::MachineApplicable
,
486 /// Returns true iff expr is an expression which tests whether or not
487 /// test is positive or an expression which tests whether or not test
489 /// Used for check-custom-abs function below
490 fn is_testing_positive(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>, test
: &Expr
<'_
>) -> bool
{
491 if let ExprKind
::Binary(Spanned { node: op, .. }
, left
, right
) = expr
.kind
{
493 BinOpKind
::Gt
| BinOpKind
::Ge
=> is_zero(cx
, right
) && eq_expr_value(cx
, left
, test
),
494 BinOpKind
::Lt
| BinOpKind
::Le
=> is_zero(cx
, left
) && eq_expr_value(cx
, right
, test
),
502 /// See [`is_testing_positive`]
503 fn is_testing_negative(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>, test
: &Expr
<'_
>) -> bool
{
504 if let ExprKind
::Binary(Spanned { node: op, .. }
, left
, right
) = expr
.kind
{
506 BinOpKind
::Gt
| BinOpKind
::Ge
=> is_zero(cx
, left
) && eq_expr_value(cx
, right
, test
),
507 BinOpKind
::Lt
| BinOpKind
::Le
=> is_zero(cx
, right
) && eq_expr_value(cx
, left
, test
),
515 /// Returns true iff expr is some zero literal
516 fn is_zero(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) -> bool
{
517 match constant_simple(cx
, cx
.typeck_results(), expr
) {
518 Some(Constant
::Int(i
)) => i
== 0,
519 Some(Constant
::F32(f
)) => f
== 0.0,
520 Some(Constant
::F64(f
)) => f
== 0.0,
525 /// If the two expressions are negations of each other, then it returns
526 /// a tuple, in which the first element is true iff expr1 is the
527 /// positive expressions, and the second element is the positive
528 /// one of the two expressions
529 /// If the two expressions are not negations of each other, then it
531 fn are_negated
<'a
>(cx
: &LateContext
<'_
>, expr1
: &'a Expr
<'a
>, expr2
: &'a Expr
<'a
>) -> Option
<(bool
, &'a Expr
<'a
>)> {
532 if let ExprKind
::Unary(UnOp
::Neg
, expr1_negated
) = &expr1
.kind
{
533 if eq_expr_value(cx
, expr1_negated
, expr2
) {
534 return Some((false, expr2
));
537 if let ExprKind
::Unary(UnOp
::Neg
, expr2_negated
) = &expr2
.kind
{
538 if eq_expr_value(cx
, expr1
, expr2_negated
) {
539 return Some((true, expr1
));
545 fn check_custom_abs(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) {
547 if let Some(higher
::If { cond, then, r#else: Some(r#else) }
) = higher
::If
::hir(expr
);
548 let if_body_expr
= peel_blocks(then
);
549 let else_body_expr
= peel_blocks(r
#else);
550 if let Some((if_expr_positive
, body
)) = are_negated(cx
, if_body_expr
, else_body_expr
);
552 let positive_abs_sugg
= (
553 "manual implementation of `abs` method",
554 format
!("{}.abs()", Sugg
::hir(cx
, body
, "..").maybe_par()),
556 let negative_abs_sugg
= (
557 "manual implementation of negation of `abs` method",
558 format
!("-{}.abs()", Sugg
::hir(cx
, body
, "..").maybe_par()),
560 let sugg
= if is_testing_positive(cx
, cond
, body
) {
561 if if_expr_positive
{
566 } else if is_testing_negative(cx
, cond
, body
) {
567 if if_expr_positive
{
582 Applicability
::MachineApplicable
,
588 fn are_same_base_logs(cx
: &LateContext
<'_
>, expr_a
: &Expr
<'_
>, expr_b
: &Expr
<'_
>) -> bool
{
590 if let ExprKind
::MethodCall(PathSegment { ident: method_name_a, .. }
, _
, args_a
, _
) = expr_a
.kind
;
591 if let ExprKind
::MethodCall(PathSegment { ident: method_name_b, .. }
, _
, args_b
, _
) = expr_b
.kind
;
593 return method_name_a
.as_str() == method_name_b
.as_str() &&
594 args_a
.len() == args_b
.len() &&
596 ["ln", "log2", "log10"].contains(&method_name_a
.as_str()) ||
597 method_name_a
.as_str() == "log" && args_a
.len() == 1 && eq_expr_value(cx
, &args_a
[0], &args_b
[0])
605 fn check_log_division(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) {
606 // check if expression of the form x.logN() / y.logN()
608 if let ExprKind
::Binary(
610 node
: BinOpKind
::Div
, ..
615 if are_same_base_logs(cx
, lhs
, rhs
);
616 if let ExprKind
::MethodCall(_
, largs_self
, ..) = &lhs
.kind
;
617 if let ExprKind
::MethodCall(_
, rargs_self
, ..) = &rhs
.kind
;
623 "log base can be expressed more clearly",
625 format
!("{}.log({})", Sugg
::hir(cx
, largs_self
, "..").maybe_par(), Sugg
::hir(cx
, rargs_self
, ".."),),
626 Applicability
::MachineApplicable
,
632 fn check_radians(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) {
634 if let ExprKind
::Binary(
636 node
: BinOpKind
::Div
, ..
641 if let ExprKind
::Binary(
643 node
: BinOpKind
::Mul
, ..
648 if let Some((rvalue
, _
)) = constant(cx
, cx
.typeck_results(), div_rhs
);
649 if let Some((lvalue
, _
)) = constant(cx
, cx
.typeck_results(), mul_rhs
);
651 // TODO: also check for constant values near PI/180 or 180/PI
652 if (F32(f32_consts
::PI
) == rvalue
|| F64(f64_consts
::PI
) == rvalue
) &&
653 (F32(180_f32) == lvalue
|| F64(180_f64) == lvalue
)
655 let mut proposal
= format
!("{}.to_degrees()", Sugg
::hir(cx
, mul_lhs
, "..").maybe_par());
657 if let ExprKind
::Lit(ref literal
) = mul_lhs
.kind
;
658 if let ast
::LitKind
::Float(ref value
, float_type
) = literal
.node
;
659 if float_type
== ast
::LitFloatType
::Unsuffixed
;
661 if value
.as_str().ends_with('
.'
) {
662 proposal
= format
!("{}0_f64.to_degrees()", Sugg
::hir(cx
, mul_lhs
, ".."));
664 proposal
= format
!("{}_f64.to_degrees()", Sugg
::hir(cx
, mul_lhs
, ".."));
672 "conversion to degrees can be done more accurately",
675 Applicability
::MachineApplicable
,
678 (F32(180_f32) == rvalue
|| F64(180_f64) == rvalue
) &&
679 (F32(f32_consts
::PI
) == lvalue
|| F64(f64_consts
::PI
) == lvalue
)
681 let mut proposal
= format
!("{}.to_radians()", Sugg
::hir(cx
, mul_lhs
, "..").maybe_par());
683 if let ExprKind
::Lit(ref literal
) = mul_lhs
.kind
;
684 if let ast
::LitKind
::Float(ref value
, float_type
) = literal
.node
;
685 if float_type
== ast
::LitFloatType
::Unsuffixed
;
687 if value
.as_str().ends_with('
.'
) {
688 proposal
= format
!("{}0_f64.to_radians()", Sugg
::hir(cx
, mul_lhs
, ".."));
690 proposal
= format
!("{}_f64.to_radians()", Sugg
::hir(cx
, mul_lhs
, ".."));
698 "conversion to radians can be done more accurately",
701 Applicability
::MachineApplicable
,
708 impl<'tcx
> LateLintPass
<'tcx
> for FloatingPointArithmetic
{
709 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
710 // All of these operations are currently not const.
711 if in_constant(cx
, expr
.hir_id
) {
715 if let ExprKind
::MethodCall(path
, receiver
, args
, _
) = &expr
.kind
{
716 let recv_ty
= cx
.typeck_results().expr_ty(receiver
);
718 if recv_ty
.is_floating_point() {
719 match path
.ident
.name
.as_str() {
720 "ln" => check_ln1p(cx
, expr
, receiver
),
721 "log" => check_log_base(cx
, expr
, receiver
, args
),
722 "powf" => check_powf(cx
, expr
, receiver
, args
),
723 "powi" => check_powi(cx
, expr
, receiver
, args
),
724 "sqrt" => check_hypot(cx
, expr
, receiver
),
729 check_expm1(cx
, expr
);
730 check_mul_add(cx
, expr
);
731 check_custom_abs(cx
, expr
);
732 check_log_division(cx
, expr
);
733 check_radians(cx
, expr
);