Skip to content

Commit 23b0b1d

Browse files
committed
[fxp] Fix special cases in Fixed::checked_add_generic.
This fixes two classes of bugs in fixed-point addition. First, in the existing special case for handling addition of fixed-points with different scales where shifting the addend with the smaller scale left yielded a value that did not fit in i128, the special case was wrong when the result's scale differed from that of the added with the larger scale. This fixes the problem and adds a test. Second, the code did not handle the special case where the sum of the addends does not fit in i128 in the larger of the scales of the addends but it would fit with the scale of the result. This also fixes that bug and adds a test. Signed-off-by: Ben Pfaff <blp@feldera.com>
1 parent 5ffd9a4 commit 23b0b1d

File tree

1 file changed

+44
-15
lines changed

1 file changed

+44
-15
lines changed

crates/fxp/src/fixed.rs

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -717,31 +717,48 @@ impl<const P0: usize, const S0: usize> Fixed<P0, S0> {
717717
match S0.cmp(&S1) {
718718
Ordering::Less => {
719719
let factor = pow10(S1 - S0);
720-
if self.0 <= i128::MAX / factor / 10 {
721-
Fixed::try_new_with_exponent(
722-
other.0.checked_add(self.0 * factor)?,
723-
S2 as i32 - S1 as i32,
724-
)
720+
if let Some(shifted) = self.0.checked_mul(factor)
721+
&& let Some(sum) = other.0.checked_add(shifted)
722+
{
723+
// This is the common case, where we can shift `self` left
724+
// to match `other` and add `other` without intermediate
725+
// overflow.
726+
Fixed::try_new_with_exponent(sum, S2 as i32 - S1 as i32)
725727
} else {
728+
// If the result type has more digits to the left of the
729+
// decimal point than `other`, then we still might be able
730+
// to compute a correct answer without ultimate overflow.
726731
let result = (I256::from_product(self.0, factor) + I256::from(other.0))
727-
.narrowing_div(pow10(S2.saturating_sub(S1)))?;
728-
Fixed::try_new_with_exponent(result, S1.saturating_sub(S2) as i32)
732+
.narrowing_div(pow10(S1.saturating_sub(S2)))?;
733+
Fixed::try_new_with_exponent(result, (S2.saturating_sub(S1)) as i32)
729734
}
730735
}
731736
Ordering::Equal => {
732-
Fixed::try_new_with_exponent(self.0.checked_add(other.0)?, S2 as i32 - S0 as i32)
737+
if let Some(sum) = self.0.checked_add(other.0) {
738+
// This is the common case, where the result fits in `i128`.
739+
Fixed::try_new_with_exponent(sum, S2 as i32 - S0 as i32)
740+
} else if S2 < S0 {
741+
// The ultimate result might fit in `i128` but we need
742+
// multiple-precision arithmetic.
743+
Fixed::try_new(
744+
(I256::from(self.0) + I256::from(other.0)).narrowing_div(pow10(S0 - S2))?,
745+
)
746+
} else {
747+
// Definitely does not fit.
748+
None
749+
}
733750
}
734751
Ordering::Greater => {
752+
// Mirror of the `Less` case.
735753
let factor = pow10(S0 - S1);
736-
if other.0 <= i128::MAX / factor / 10 {
737-
Fixed::try_new_with_exponent(
738-
self.0.checked_add(other.0 * factor)?,
739-
S2 as i32 - S0 as i32,
740-
)
754+
if let Some(shifted) = other.0.checked_mul(factor)
755+
&& let Some(sum) = self.0.checked_add(shifted)
756+
{
757+
Fixed::try_new_with_exponent(sum, S2 as i32 - S0 as i32)
741758
} else {
742759
let result = (I256::from_product(other.0, factor) + I256::from(self.0))
743-
.narrowing_div(pow10(S2.saturating_sub(S0)))?;
744-
Fixed::try_new_with_exponent(result, S0.saturating_sub(S2) as i32)
760+
.narrowing_div(pow10(S0.saturating_sub(S2)))?;
761+
Fixed::try_new_with_exponent(result, S2.saturating_sub(S0) as i32)
745762
}
746763
}
747764
}
@@ -1537,6 +1554,18 @@ mod test {
15371554
assert_eq!(f(1.23) + f(-2.34), f(-1.11));
15381555
assert_eq!(f(-1.23) + f(-2.34), f(-3.57));
15391556

1557+
// Cases that require avoiding intermediate overflow.
1558+
let af: Fixed<38, 35> = Fixed::try_from(999).unwrap();
1559+
let bf: Fixed<37, 34> = Fixed::try_from(999).unwrap();
1560+
let cf: Fixed<36, 30> = af.checked_add_generic(bf).unwrap();
1561+
assert_eq!(cf, 1998);
1562+
let df: Fixed<36, 30> = bf.checked_add_generic(af).unwrap();
1563+
assert_eq!(df, 1998);
1564+
let ef: Fixed<36, 3> = af.checked_add_generic(af).unwrap();
1565+
assert_eq!(ef, 1998);
1566+
let ff: Option<Fixed<38, 35>> = af.checked_add_generic(af);
1567+
assert_eq!(ff, None);
1568+
15401569
// General case.
15411570
for a in -999..=999 {
15421571
let af: Fixed<10, 2> = Fixed(a);

0 commit comments

Comments
 (0)