Skip to content

Commit 6d83547

Browse files
orpuente-MSsezna
andauthored
Add lint for double (in)equality (#2104)
This PR adds a lint for double equality and inequality. It also updates the DF Chemistry sample to perform a correct `NAN` check. --------- Co-authored-by: Alex Hansen <alex@alex-hansen.com>
1 parent f383f75 commit 6d83547

File tree

4 files changed

+88
-6
lines changed

4 files changed

+88
-6
lines changed

compiler/qsc_linter/src/lints/hir.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ use std::rc::Rc;
66
use qsc_data_structures::span::Span;
77
use qsc_hir::{
88
hir::{
9-
CallableDecl, CallableKind, Expr, ExprKind, Field, ItemKind, Res, SpecBody, SpecDecl, Stmt,
10-
StmtKind,
9+
BinOp, CallableDecl, CallableKind, Expr, ExprKind, Field, ItemKind, Res, SpecBody,
10+
SpecDecl, Stmt, StmtKind,
1111
},
12-
ty::Ty,
12+
ty::{Prim, Ty},
1313
visit::{self, Visitor},
1414
};
1515

@@ -35,12 +35,42 @@ use super::lint;
3535
// For more details on how to add a lint, please refer to the crate-level documentation
3636
// in qsc_linter/lib.rs.
3737
declare_hir_lints! {
38+
(DoubleEquality, LintLevel::Warn, "strict comparison of doubles", "consider comparing them with some margin of error"),
3839
(NeedlessOperation, LintLevel::Allow, "operation does not contain any quantum operations", "this callable can be declared as a function instead"),
3940
(DeprecatedFunctionConstructor, LintLevel::Allow, "deprecated function constructors", "function constructors for struct types are deprecated, use `new` instead"),
4041
(DeprecatedWithOperator, LintLevel::Allow, "deprecated `w/` and `w/=` operators for structs", "`w/` and `w/=` operators for structs are deprecated, use `new` instead"),
4142
(DeprecatedDoubleColonOperator, LintLevel::Allow, "deprecated `::` for field access", "`::` operator is deprecated, use `.` instead"),
4243
}
4344

45+
#[derive(Default)]
46+
struct DoubleEquality {
47+
level: LintLevel,
48+
}
49+
50+
impl HirLintPass for DoubleEquality {
51+
fn check_expr(&mut self, expr: &Expr, buffer: &mut Vec<Lint>, _compilation: Compilation) {
52+
// Ignore the case where the identifier is being compared for equality with itself,
53+
// Since this is used to check if the double is `NaN`. This will be compiled to
54+
// fcmp oeq value value
55+
// in LLVM because we only implemented the ord side of fcmp.
56+
// See https://llvm.org/docs/LangRef.html#id313 for more details.
57+
if let ExprKind::BinOp(BinOp::Eq, ref lhs, ref rhs) = expr.kind {
58+
if let (ExprKind::Var(lhs_id, _), ExprKind::Var(rhs_id, _)) = (&lhs.kind, &rhs.kind) {
59+
if lhs_id == rhs_id {
60+
return;
61+
}
62+
}
63+
}
64+
65+
if let ExprKind::BinOp(BinOp::Eq | BinOp::Neq, ref lhs, ref rhs) = expr.kind {
66+
if matches!(lhs.ty, Ty::Prim(Prim::Double)) && matches!(rhs.ty, Ty::Prim(Prim::Double))
67+
{
68+
buffer.push(lint!(self, expr.span));
69+
}
70+
}
71+
}
72+
}
73+
4474
/// Helper to check if an operation has desired operation characteristics
4575
///
4676
/// empty operations: no lint, special case of `I` operation used for Delay

compiler/qsc_linter/src/tests.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,58 @@ fn division_by_zero() {
214214
);
215215
}
216216

217+
#[test]
218+
fn double_equality() {
219+
check(
220+
&wrap_in_callable("1.0 == 1.01;", CallableKind::Function),
221+
&expect![[r#"
222+
[
223+
SrcLint {
224+
source: "1.0 == 1.01",
225+
level: Warn,
226+
message: "strict comparison of doubles",
227+
help: "consider comparing them with some margin of error",
228+
code_action_edits: [],
229+
},
230+
]
231+
"#]],
232+
);
233+
}
234+
235+
#[test]
236+
fn check_double_equality_with_itself_is_allowed_for_nan_check() {
237+
check(
238+
&wrap_in_callable(
239+
r#"
240+
let a = 1.0;
241+
let is_nan = not (a == a);
242+
"#,
243+
CallableKind::Function,
244+
),
245+
&expect![[r#"
246+
[]
247+
"#]],
248+
);
249+
}
250+
251+
#[test]
252+
fn double_inequality() {
253+
check(
254+
&wrap_in_callable("1.0 != 1.01;", CallableKind::Function),
255+
&expect![[r#"
256+
[
257+
SrcLint {
258+
source: "1.0 != 1.01",
259+
level: Warn,
260+
message: "strict comparison of doubles",
261+
help: "consider comparing them with some margin of error",
262+
code_action_edits: [],
263+
},
264+
]
265+
"#]],
266+
);
267+
}
268+
217269
#[test]
218270
fn needless_parens_in_assignment() {
219271
check(

samples/estimation/df-chemistry/src/DoubleFactorizedChemistry.qs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ internal function AllEigenVectorsAsBitString(eigenVectors : Double[][], precisio
341341
for index in 0..Length(eigenVector) - 2 {
342342
// We apply MinD, such that rounding errors do not lead to
343343
// an argument for ArcCos which is larger than 1.0. (p. 52, eq. 56)
344-
let theta = sins == 0.0 ? 0.0 | 0.5 * ArcCos(MinD(eigenVector[index] / sins, 1.0));
344+
let theta = not (AbsD(sins) > 0.0) ? 0.0 | 0.5 * ArcCos(MinD(eigenVector[index] / sins, 1.0));
345345

346346
// all angles as bit string
347347
let factor = theta / tau;
@@ -357,5 +357,5 @@ internal function AllEigenVectorsAsBitString(eigenVectors : Double[][], precisio
357357
}
358358

359359
internal function IsNaN(value : Double) : Bool {
360-
value != value
360+
not (value == value)
361361
}

samples/estimation/df-chemistry/src/Prepare.qs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ internal function DiscretizedProbabilityDistribution(bitsPrecision : Int, coeffi
116116
let nCoefficients = Length(coefficients);
117117
Fact(bitsPrecision <= 31, $"Bits of precision {bitsPrecision} unsupported. Max is 31.");
118118
Fact(nCoefficients > 1, "Cannot prepare state with less than 2 coefficients.");
119-
Fact(oneNorm != 0.0, "State must have at least one coefficient > 0");
119+
Fact(AbsD(oneNorm) > 0.0, "State must have at least one coefficient > 0");
120120

121121
let barHeight = 2^bitsPrecision - 1;
122122

0 commit comments

Comments
 (0)