3 use rustc::ty::TypeVariants;
8 use syntax_pos::symbol::Symbol;
9 use crate::utils::span_lint_and_sugg;
11 /// **What it does:** Checks for float literals with a precision greater
12 /// than that supported by the underlying type
14 /// **Why is this bad?** Rust will truncate the literal silently.
16 /// **Known problems:** None.
22 /// Insert a short example of code that triggers the lint
23 /// let v: f32 = 0.123_456_789_9;
24 /// println!("{}", v); // 0.123_456_789
27 /// Insert a short example of improved code that doesn't trigger the lint
28 /// let v: f64 = 0.123_456_789_9;
29 /// println!("{}", v); // 0.123_456_789_9
31 declare_clippy_lint! {
32 pub EXCESSIVE_PRECISION,
34 "excessive precision for float literal"
37 pub struct ExcessivePrecision;
39 impl LintPass for ExcessivePrecision {
40 fn get_lints(&self) -> LintArray {
41 lint_array!(EXCESSIVE_PRECISION)
45 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ExcessivePrecision {
46 fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx hir::Expr) {
48 let ty = cx.tables.expr_ty(expr);
49 if let TypeVariants::TyFloat(fty) = ty.sty;
50 if let hir::ExprLit(ref lit) = expr.node;
51 if let LitKind::Float(sym, _) | LitKind::FloatUnsuffixed(sym) = lit.node;
52 if let Some(sugg) = self.check(sym, fty);
58 "float has excessive precision",
59 "consider changing the type or truncating it to",
67 impl ExcessivePrecision {
68 // None if nothing to lint, Some(suggestion) if lint neccessary
69 fn check(&self, sym: Symbol, fty: FloatTy) -> Option<String> {
70 let max = max_digits(fty);
71 let sym_str = sym.as_str();
72 if dot_zero_exclusion(&sym_str) {
75 // Try to bail out if the float is for sure fine.
76 // If its within the 2 decimal digits of being out of precision we
77 // check if the parsed representation is the same as the string
78 // since we'll need the truncated string anyway.
79 let digits = count_digits(&sym_str);
80 if digits > max as usize {
81 let formatter = FloatFormat::new(&sym_str);
83 FloatTy::F32 => sym_str.parse::<f32>().map(|f| formatter.format(f)),
84 FloatTy::F64 => sym_str.parse::<f64>().map(|f| formatter.format(f)),
86 // We know this will parse since we are in LatePass
92 let di = super::literal_representation::DigitInfo::new(&s, true);
93 Some(di.grouping_hint())
101 /// Should we exclude the float because it has a .0 suffix
102 /// Ex 1_000_000_000.0
103 fn dot_zero_exclusion(s: &str) -> bool {
104 if let Some(after_dec) = s.split('.').nth(1) {
105 let mut decpart = after_dec
107 .take_while(|c| *c != 'e' || *c != 'E');
109 match decpart.next() {
110 Some('0') => decpart.count() == 0,
118 fn max_digits(fty: FloatTy) -> u32 {
120 FloatTy::F32 => f32::DIGITS,
121 FloatTy::F64 => f64::DIGITS,
125 /// Counts the digits excluding leading zeros
126 fn count_digits(s: &str) -> usize {
127 // Note that s does not contain the f32/64 suffix
129 .filter(|c| *c != '-' || *c != '.')
130 .take_while(|c| *c != 'e' || *c != 'E')
131 .fold(0, |count, c| {
133 if c == '0' && count == 0 {
147 fn new(s: &str) -> Self {
149 .find_map(|x| match x {
150 'e' => Some(FloatFormat::LowerExp),
151 'E' => Some(FloatFormat::UpperExp),
154 .unwrap_or(FloatFormat::Normal)
156 fn format<T>(&self, f: T) -> String
157 where T: fmt::UpperExp + fmt::LowerExp + fmt::Display {
159 FloatFormat::LowerExp => format!("{:e}", f),
160 FloatFormat::UpperExp => format!("{:E}", f),
161 FloatFormat::Normal => format!("{}", f),