Skip to content

Commit b55136a

Browse files
committed
Use floatbv_mod for floating-point remainder operator
The remainder (%) operator on floating-point types was incorrectly using CBMC's integer 'mod' operation, which CBMC silently ignores for floatbv types, producing incorrect verification results. Use the floatbv_mod binary operator for float/double operands, which correctly implements IEEE 754 fmod semantics matching Rust's % on floating-point types. Resolves #2669 Signed-off-by: Felipe R. Monteiro <felisous@amazon.com>
1 parent 39988dc commit b55136a

4 files changed

Lines changed: 42 additions & 3 deletions

File tree

cprover_bindings/src/goto_program/expr.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ pub enum BinaryOperator {
203203
Bitxor,
204204
Div,
205205
Equal,
206+
FloatbvMod,
206207
FloatbvRoundToIntegral,
207208
Ge,
208209
Gt,
@@ -1033,7 +1034,8 @@ impl Expr {
10331034
|| (lhs.typ.is_pointer() && rhs.typ.is_integer())
10341035
}
10351036
// Arithmetic
1036-
Div | Mod | Mult => lhs.typ == rhs.typ && (lhs.typ.is_numeric() || lhs.typ.is_vector()),
1037+
Div | Mult => lhs.typ == rhs.typ && (lhs.typ.is_numeric() || lhs.typ.is_vector()),
1038+
Mod => lhs.typ == rhs.typ && (lhs.typ.is_integer() || lhs.typ.is_vector()),
10371039
// Bitshifts
10381040
Ashr | Lshr | Shl => {
10391041
lhs.typ.is_integer() && rhs.typ.is_integer()
@@ -1075,6 +1077,7 @@ impl Expr {
10751077
"vector comparison operators must be typechecked by `typecheck_vector_cmp_expr`"
10761078
)
10771079
}
1080+
FloatbvMod => lhs.typ == rhs.typ && lhs.typ.is_floating_point(),
10781081
FloatbvRoundToIntegral => lhs.typ.is_floating_point() && rhs.typ.is_integer(),
10791082
}
10801083
}
@@ -1090,7 +1093,7 @@ impl Expr {
10901093
}
10911094
}
10921095
// Arithmetic
1093-
Div | Mod | Mult | Plus => lhs.typ.clone(),
1096+
Div | FloatbvMod | Mod | Mult | Plus => lhs.typ.clone(),
10941097
// Bitshifts
10951098
Ashr | Lshr | Rol | Ror | Shl => lhs.typ.clone(),
10961099
// Boolean ops
@@ -1186,8 +1189,12 @@ impl Expr {
11861189
}
11871190

11881191
/// `self % e`
1192+
/// `self % e`. For floating-point types, emits CBMC's `floatbv_mod` which
1193+
/// implements C `fmod` semantics (matching Rust's `%` on floats via LLVM `frem`).
1194+
/// Unlike other `floatbv_*` ops, `floatbv_mod` does not take a rounding mode
1195+
/// parameter — the result is exact per IEEE 754.
11891196
pub fn rem(self, e: Expr) -> Expr {
1190-
self.binop(Mod, e)
1197+
if self.typ().is_floating_point() { self.binop(FloatbvMod, e) } else { self.binop(Mod, e) }
11911198
}
11921199

11931200
/// `self && e`

cprover_bindings/src/irep/irep_id.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,7 @@ pub enum IrepId {
565565
FloatbvMinus,
566566
FloatbvMult,
567567
FloatbvDiv,
568+
FloatbvMod,
568569
FloatbvRem,
569570
FloatbvTypecast,
570571
CompoundLiteral,
@@ -1455,6 +1456,7 @@ impl IrepId {
14551456
IrepId::FloatbvMinus => "floatbv_minus",
14561457
IrepId::FloatbvMult => "floatbv_mult",
14571458
IrepId::FloatbvDiv => "floatbv_div",
1459+
IrepId::FloatbvMod => "floatbv_mod",
14581460
IrepId::FloatbvRem => "floatbv_rem",
14591461
IrepId::FloatbvTypecast => "floatbv_typecast",
14601462
IrepId::CompoundLiteral => "compound_literal",

cprover_bindings/src/irep/to_irep.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ impl ToIrepId for BinaryOperator {
6060
BinaryOperator::Bitxor => IrepId::Bitxor,
6161
BinaryOperator::Div => IrepId::Div,
6262
BinaryOperator::Equal => IrepId::Equal,
63+
BinaryOperator::FloatbvMod => IrepId::FloatbvMod,
6364
BinaryOperator::FloatbvRoundToIntegral => IrepId::FloatbvRoundToIntegral,
6465
BinaryOperator::Ge => IrepId::Ge,
6566
BinaryOperator::Gt => IrepId::Gt,
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright Kani Contributors
2+
// SPDX-License-Identifier: Apache-2.0 OR MIT
3+
//
4+
//! Regression test for https://github.com/model-checking/kani/issues/2669
5+
//! Floating-point remainder (%) was producing incorrect results because
6+
//! CBMC's integer `mod` was used instead of `floatbv_mod`.
7+
8+
/// Symbolic f32 remainder: verify the fmod invariant |result| < |divisor|.
9+
/// Inputs are i8-to-f32 casts, so both positive and negative values are
10+
/// exercised without introducing NaN/infinity edge cases.
11+
#[kani::proof]
12+
fn check_f32_rem() {
13+
let dividend: f32 = kani::any::<i8>().into();
14+
let divisor: f32 = kani::any::<i8>().into();
15+
kani::assume(divisor != 0.0);
16+
let result = dividend % divisor;
17+
// fmod invariant: |result| < |divisor|
18+
assert!(result.abs() < divisor.abs());
19+
}
20+
21+
/// Symbolic f64 remainder: same fmod invariant for f64.
22+
#[kani::proof]
23+
fn check_f64_rem() {
24+
let a: f64 = kani::any::<i8>().into();
25+
let b: f64 = kani::any::<i8>().into();
26+
kani::assume(b != 0.0);
27+
let result = a % b;
28+
assert!(result.abs() < b.abs());
29+
}

0 commit comments

Comments
 (0)