Repository: rust-lang/rust-clippy (Rust)

Issue:
`excessive_precision` inconsistency with exponential notation
I love the idea of the `excessive_precision` lint. I'm working on a parser for a 3rd-party, closed-source file format, and they must have weird float-to-string serialization code. The value in the file doesn't match the one in the UI (e.g. 6.93 serialized to 6.9299999999999997E+00. I know floats are hard and weird, and 6.93 might not be an exact value, but to serialize out a string that's beyond double-precision? Maybe it's technically correct. `lexical-core` doesn't think so, though). It's really handy to know that my unit test values aren't being silently truncated.

Most cases work just fine. However, I came across a case where using decimal and exponential literals causes `clippy` to disagree with itself.

```rust
#[test]
fn clippy_excessive_precision() {
    let decimal: f64 = 0.004886506780521244;

    // This trips `clippy::excessive_precision`
    let exponential: f64 = 4.886506780521244E-03;

    // Truncated `exponential`
    let clippy: f64 = 4.88650678052124E-03;

    // NOTE: `clippy::float_cmp` warns about the `assert_eq`s, but I'm evaluating binary identity...
    //       so I think this is appropriate?

    assert_eq!(decimal, exponential);

    // `clippy` seems to suggest these should be equal, but they're not
    assert_ne!(exponential, clippy);
    // ... but they ARE within the error margin. Platform issue, maybe?
    assert!((exponential - clippy).abs() < f64::EPSILON);
}
```
### Meta

- `cargo clippy -V`: clippy 0.0.212 (18bf6b4f0 2020-10-07)
- `rustc -Vv`:
  ```
  rustc 1.47.0 (18bf6b4f0 2020-10-07)
  binary: rustc
  commit-hash: 18bf6b4f01a6feaf7259ba7cdae58031af1b7b39
  commit-date: 2020-10-07
  host: x86_64-pc-windows-msvc
  release: 1.47.0
  LLVM version: 11.0
  ```


Discussion:
OH WAIT. Maybe this is actually/additionally a documentation issue?

I used `rust-analyzer`'s auto-fix thing, and it just inserted underscores without truncating. As if it tripped `clippy::unreadable_literal` instead? `clippy` no longer complains.

```rust
#[test]
fn clippy_excessive_precision() {
    let decimal: f64 = 0.004886506780521244;

    // This trips `clippy::excessive_precision`
    let exponential: f64 = 4.886506780521244E-03;

    // Auto-fixed `exponential`
    let clippy: f64 = 4.886_506_780_521_244E-3;

    assert_eq!(decimal, exponential);

    // These are equal now, as expected
    assert_eq!(exponential, clippy);
}
```

The [documentation](https://rust-lang.github.io/rust-clippy/master/#excessive_precision) for `clippy::excessive_precision` doesn't say anything about underscores. The only "**Why is this bad**" is silent truncation. Also, it doesn't complain about `decimal`.
@rustbot modify labels: +L-documentation -L-bug
So if you look at the error message carefully, you'll see, that this lint doesn't complain about the fractional part, but about the exponent part for some reason:

```rust
warning: float has excessive precision
 --> src/lib.rs:5:28
  |
5 |     let exponential: f64 = 4.886506780521244E-03;
  |                            ^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `4.886_506_780_521_244E-3`
  |
  = note: `#[warn(clippy::excessive_precision)]` on by default
  = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#excessive_precision
```

If you change `...E-03` to `...E-3` it stops complaining. This is definitely a bug, because this lint should not get triggered by the exponential part.
OOOH, you're totally right. I was so fixated on the underscores that I didn't notice the zero.

I imagine it's because "precision" is being measured by "count all digit characters in literal after the decimal point"?
> I imagine it's because "precision" is being measured by "count all digit characters in literal after the decimal point"?

I think we parse the literal and then compare their string representations or something like that, yeah.
Probably related to this, it also doesn't like the plus sign being used in exponents. E.g. trying to assign the following to an `f64`:

`let x: f64 = 1.995_840_309_534_719_6e+292; // 0x1.fffffffffffffp+970`

results in

```
let x: f64 = 1.995_840_309_534_719_6e+292; // 0x1.fffffffffffffp+970
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider changing the type or truncating it to: `1.995_840_309_534_719_6e292`
```

I personally prefer including the sign as it makes it more obvious where the separation between the sections is. Even if this is something that's worth linting, it's not a precision issue or a case where changing the type is appropriate, and the suggested fix should not be "truncation" (which at least for me suggests cutting off the *end* of the representation).



Patch:
diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs
index 012ad8e1a229..c51267567d01 100644
--- a/clippy_lints/src/float_literal.rs
+++ b/clippy_lints/src/float_literal.rs
@@ -126,7 +126,7 @@ impl<'tcx> LateLintPass<'tcx> for FloatLiteral {
                         },
                     );
                 }
-            } else if digits > max as usize && float_str.len() < sym_str.len() {
+            } else if digits > max as usize && count_digits(&float_str) < count_digits(sym_str) {
                 span_lint_and_then(
                     cx,
                     EXCESSIVE_PRECISION,


Test patch:
diff --git a/tests/ui/excessive_precision.fixed b/tests/ui/excessive_precision.fixed
index 99d09774d16d..8a8c2e1939c8 100644
--- a/tests/ui/excessive_precision.fixed
+++ b/tests/ui/excessive_precision.fixed
@@ -79,6 +79,9 @@ fn main() {
     // issue #2840
     let num = 0.000_000_000_01e-10f64;
 
+    // issue #6341
+    let exponential: f64 = 4.886506780521244E-03;
+
     // issue #7744
     let _ = 2.225_073_858_507_201e-308_f64;
     //~^ excessive_precision
diff --git a/tests/ui/excessive_precision.rs b/tests/ui/excessive_precision.rs
index a542fb2e7e3c..5dcf55cb9273 100644
--- a/tests/ui/excessive_precision.rs
+++ b/tests/ui/excessive_precision.rs
@@ -79,6 +79,9 @@ fn main() {
     // issue #2840
     let num = 0.000_000_000_01e-10f64;
 
+    // issue #6341
+    let exponential: f64 = 4.886506780521244E-03;
+
     // issue #7744
     let _ = 2.225_073_858_507_201_1e-308_f64;
     //~^ excessive_precision
diff --git a/tests/ui/excessive_precision.stderr b/tests/ui/excessive_precision.stderr
index 934a367e1065..f5eeadf0c8cb 100644
--- a/tests/ui/excessive_precision.stderr
+++ b/tests/ui/excessive_precision.stderr
@@ -157,7 +157,7 @@ LL +     let bad_bige32: f32 = 1.123_456_8E-10;
    |
 
 error: float has excessive precision
-  --> tests/ui/excessive_precision.rs:83:13
+  --> tests/ui/excessive_precision.rs:86:13
    |
 LL |     let _ = 2.225_073_858_507_201_1e-308_f64;
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -169,7 +169,7 @@ LL +     let _ = 2.225_073_858_507_201e-308_f64;
    |
 
 error: float has excessive precision
-  --> tests/ui/excessive_precision.rs:87:13
+  --> tests/ui/excessive_precision.rs:90:13
    |
 LL |     let _ = 1.000_000_000_000_001e-324_f64;
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -181,7 +181,7 @@ LL +     let _ = 0_f64;
    |
 
 error: float has excessive precision
-  --> tests/ui/excessive_precision.rs:98:20
+  --> tests/ui/excessive_precision.rs:101:20
    |
 LL |     const _: f64 = 3.0000000000000000e+00;
    |                    ^^^^^^^^^^^^^^^^^^^^^^