Skip to content

Commit 3cd0b18

Browse files
Merge #9: Prevent intervals from exploding
2 parents 4d1f392 + de1068d commit 3cd0b18

1 file changed

Lines changed: 118 additions & 5 deletions

File tree

src/algebraic_numbers.rs

Lines changed: 118 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ trait RootSelector: Sized {
990990
.collect();
991991
let mut factors_temp = Vec::with_capacity(factors.len());
992992
loop {
993-
let interval = self.get_interval();
993+
let mut interval = self.get_interval();
994994
let (lower_bound, upper_bound) = interval.to_ratio_range();
995995
mem::swap(&mut factors, &mut factors_temp);
996996
factors.clear();
@@ -1019,10 +1019,94 @@ trait RootSelector: Sized {
10191019
}
10201020
}
10211021
if roots_left <= 1 {
1022-
break RealAlgebraicNumber::new_unchecked(
1023-
factors.remove(0).into_factor(),
1024-
interval,
1025-
);
1022+
let factor = factors.remove(0);
1023+
if factor.factor().degree().is_some_and(|deg| deg > 1) {
1024+
// Reduce the precision of the interval as much as possible to prevent the precision from exploding due to repeated computation
1025+
// Skip this step if the number is rational
1026+
1027+
let mut precomputed_lower_bound_sign_changes = None;
1028+
let mut precomputed_upper_bound_sign_changes = None;
1029+
1030+
while interval.log2_denom() > 0 {
1031+
let trial_interval = interval
1032+
.clone()
1033+
.into_converted_log2_denom(interval.log2_denom() - 1);
1034+
1035+
let lower_bound_sign_changes = sign_changes_at(
1036+
&factor.primitive_sturm_sequence,
1037+
ValueOrInfinity::Value(&trial_interval.lower_bound()),
1038+
);
1039+
let upper_bound_sign_changes = sign_changes_at(
1040+
&factor.primitive_sturm_sequence,
1041+
ValueOrInfinity::Value(&trial_interval.upper_bound()),
1042+
);
1043+
// We don't need to check if the lower bound is a root because we know that the root is irrational
1044+
let num_roots = distance(
1045+
lower_bound_sign_changes.sign_change_count,
1046+
upper_bound_sign_changes.sign_change_count,
1047+
);
1048+
1049+
assert!(num_roots > 0);
1050+
1051+
if num_roots == 1 {
1052+
interval = trial_interval;
1053+
precomputed_lower_bound_sign_changes = Some(lower_bound_sign_changes);
1054+
precomputed_upper_bound_sign_changes = Some(upper_bound_sign_changes);
1055+
} else {
1056+
break;
1057+
}
1058+
}
1059+
1060+
// Tighten the range as much as possible to prevent that from exploding too
1061+
1062+
let lower_bound_sign_changes = precomputed_lower_bound_sign_changes
1063+
.unwrap_or_else(|| {
1064+
sign_changes_at(
1065+
&factor.primitive_sturm_sequence,
1066+
ValueOrInfinity::Value(&interval.lower_bound()),
1067+
)
1068+
});
1069+
let upper_bound_sign_changes = precomputed_upper_bound_sign_changes
1070+
.unwrap_or_else(|| {
1071+
sign_changes_at(
1072+
&factor.primitive_sturm_sequence,
1073+
ValueOrInfinity::Value(&interval.upper_bound()),
1074+
)
1075+
});
1076+
1077+
while interval.upper_bound_numer() - interval.lower_bound_numer()
1078+
> BigInt::from(1)
1079+
{
1080+
let middle = (interval.lower_bound_numer() + interval.upper_bound_numer())
1081+
/ BigInt::from(2);
1082+
1083+
let middle_sign_changes = sign_changes_at(
1084+
&factor.primitive_sturm_sequence,
1085+
ValueOrInfinity::Value(&Ratio::new(
1086+
middle.clone(),
1087+
BigInt::one() << interval.log2_denom(),
1088+
)),
1089+
);
1090+
1091+
if middle_sign_changes.sign_change_count
1092+
== lower_bound_sign_changes.sign_change_count
1093+
{
1094+
interval.set_lower_bound_numer(middle);
1095+
} else {
1096+
assert_eq!(
1097+
middle_sign_changes.sign_change_count,
1098+
upper_bound_sign_changes.sign_change_count
1099+
);
1100+
interval.set_upper_bound_numer(middle);
1101+
}
1102+
}
1103+
}
1104+
1105+
let v = RealAlgebraicNumber::new_unchecked(factor.into_factor(), interval);
1106+
break match v.to_rational() {
1107+
Some(rational) => RealAlgebraicNumber::from(rational),
1108+
None => v,
1109+
};
10261110
}
10271111
self.shrink_interval();
10281112
}
@@ -1665,6 +1749,35 @@ mod tests {
16651749
);
16661750
}
16671751

1752+
#[test]
1753+
fn prevent_exploding_intervals() {
1754+
let mut num = RealAlgebraicNumber::from(2) / RealAlgebraicNumber::from(3);
1755+
let original = num.clone();
1756+
1757+
for _ in 0..100 {
1758+
num = num.pow((1, 2));
1759+
num = num.pow((2, 1));
1760+
}
1761+
1762+
assert_eq!(num, original);
1763+
assert_eq!(num.interval().log2_denom(), 0);
1764+
assert_eq!(num.interval().lower_bound_numer(), &BigInt::from(0));
1765+
assert_eq!(num.interval().upper_bound_numer(), &BigInt::from(1));
1766+
}
1767+
1768+
#[test]
1769+
fn prevent_exploding_precisions() {
1770+
let v1 = -(RealAlgebraicNumber::from(2) / RealAlgebraicNumber::from(81)).pow((1, 2));
1771+
let v2 = (RealAlgebraicNumber::from(200) / RealAlgebraicNumber::from(81)).pow((1, 2));
1772+
1773+
let num = v1 + v2;
1774+
1775+
assert_eq!(num, RealAlgebraicNumber::from(2).pow((1, 2)));
1776+
assert_eq!(num.interval().log2_denom(), 0);
1777+
assert_eq!(num.interval().lower_bound_numer(), &BigInt::from(1));
1778+
assert_eq!(num.interval().upper_bound_numer(), &BigInt::from(2));
1779+
}
1780+
16681781
#[test]
16691782
fn test_add() {
16701783
fn test_case<

0 commit comments

Comments
 (0)