Skip to main content

litex/verify/verify_builtin_rules/
number_compare.rs

1use super::order_normalize::normalize_positive_order_atomic_fact;
2use crate::prelude::*;
3
4pub enum NumberCompareResult {
5    Less,
6    Equal,
7    Greater,
8}
9
10/// Compare a normalized decimal string (same shape as [`Number::normalized_value`]) to `"0"`.
11pub fn compare_normalized_number_str_to_zero(number_value: &str) -> NumberCompareResult {
12    compare_number_strings(number_value.trim(), "0")
13}
14
15fn parse_number_parts_for_comparison(number_value: &str) -> (bool, Vec<u8>, Vec<u8>) {
16    let trimmed_number_value = number_value.trim();
17    let (is_negative, magnitude_string) = if trimmed_number_value.starts_with('-') {
18        (true, trimmed_number_value[1..].trim())
19    } else {
20        (false, trimmed_number_value)
21    };
22
23    let (integer_part_string, fractional_part_string) = match magnitude_string.find('.') {
24        Some(dot_index) => (
25            &magnitude_string[..dot_index],
26            &magnitude_string[dot_index + 1..],
27        ),
28        None => (magnitude_string, ""),
29    };
30
31    let mut integer_digits: Vec<u8> = Vec::new();
32    if integer_part_string.is_empty() {
33        integer_digits.push(0);
34    } else {
35        for current_char in integer_part_string.chars() {
36            if current_char.is_ascii_digit() {
37                integer_digits.push(current_char as u8 - b'0');
38            }
39        }
40        if integer_digits.is_empty() {
41            integer_digits.push(0);
42        }
43    }
44
45    let mut fractional_digits: Vec<u8> = Vec::new();
46    for current_char in fractional_part_string.chars() {
47        if current_char.is_ascii_digit() {
48            fractional_digits.push(current_char as u8 - b'0');
49        }
50    }
51
52    (is_negative, integer_digits, fractional_digits)
53}
54
55fn digits_are_all_zero(digits: &[u8]) -> bool {
56    for digit in digits {
57        if *digit != 0 {
58            return false;
59        }
60    }
61    true
62}
63
64fn normalized_decimal_string_is_integer(number_value: &str) -> bool {
65    let (_, _integer_digits, fractional_digits) = parse_number_parts_for_comparison(number_value);
66    digits_are_all_zero(&fractional_digits)
67}
68
69fn normalized_decimal_string_is_even_integer(number_value: &str) -> bool {
70    if !normalized_decimal_string_is_integer(number_value) {
71        return false;
72    }
73    let (_is_negative, integer_digits, _fractional_digits) =
74        parse_number_parts_for_comparison(number_value);
75    let last_digit = integer_digits.last().copied().unwrap_or(0);
76    last_digit % 2 == 0
77}
78
79fn first_non_zero_integer_digit_index(integer_digits: &[u8]) -> usize {
80    let mut current_index = 0;
81    while current_index + 1 < integer_digits.len() && integer_digits[current_index] == 0 {
82        current_index += 1;
83    }
84    current_index
85}
86
87fn compare_non_negative_decimal_parts(
88    left_integer_digits: &[u8],
89    left_fractional_digits: &[u8],
90    right_integer_digits: &[u8],
91    right_fractional_digits: &[u8],
92) -> NumberCompareResult {
93    let left_integer_start_index = first_non_zero_integer_digit_index(left_integer_digits);
94    let right_integer_start_index = first_non_zero_integer_digit_index(right_integer_digits);
95
96    let left_effective_integer_len = left_integer_digits.len() - left_integer_start_index;
97    let right_effective_integer_len = right_integer_digits.len() - right_integer_start_index;
98    if left_effective_integer_len < right_effective_integer_len {
99        return NumberCompareResult::Less;
100    }
101    if left_effective_integer_len > right_effective_integer_len {
102        return NumberCompareResult::Greater;
103    }
104
105    let mut integer_index = 0;
106    while integer_index < left_effective_integer_len {
107        let left_digit = left_integer_digits[left_integer_start_index + integer_index];
108        let right_digit = right_integer_digits[right_integer_start_index + integer_index];
109        if left_digit < right_digit {
110            return NumberCompareResult::Less;
111        }
112        if left_digit > right_digit {
113            return NumberCompareResult::Greater;
114        }
115        integer_index += 1;
116    }
117
118    let fractional_compare_len = if left_fractional_digits.len() > right_fractional_digits.len() {
119        left_fractional_digits.len()
120    } else {
121        right_fractional_digits.len()
122    };
123    let mut fractional_index = 0;
124    while fractional_index < fractional_compare_len {
125        let left_digit = match left_fractional_digits.get(fractional_index) {
126            Some(digit) => *digit,
127            None => 0,
128        };
129        let right_digit = match right_fractional_digits.get(fractional_index) {
130            Some(digit) => *digit,
131            None => 0,
132        };
133        if left_digit < right_digit {
134            return NumberCompareResult::Less;
135        }
136        if left_digit > right_digit {
137            return NumberCompareResult::Greater;
138        }
139        fractional_index += 1;
140    }
141
142    NumberCompareResult::Equal
143}
144
145pub fn compare_number_strings(
146    left_number_value: &str,
147    right_number_value: &str,
148) -> NumberCompareResult {
149    let (left_is_negative, left_integer_digits, left_fractional_digits) =
150        parse_number_parts_for_comparison(left_number_value);
151    let (right_is_negative, right_integer_digits, right_fractional_digits) =
152        parse_number_parts_for_comparison(right_number_value);
153
154    let left_is_zero =
155        digits_are_all_zero(&left_integer_digits) && digits_are_all_zero(&left_fractional_digits);
156    let right_is_zero =
157        digits_are_all_zero(&right_integer_digits) && digits_are_all_zero(&right_fractional_digits);
158    if left_is_zero && right_is_zero {
159        return NumberCompareResult::Equal;
160    }
161
162    if left_is_negative && !left_is_zero && !right_is_negative {
163        return NumberCompareResult::Less;
164    }
165    if right_is_negative && !right_is_zero && !left_is_negative {
166        return NumberCompareResult::Greater;
167    }
168
169    let non_negative_compare_result = compare_non_negative_decimal_parts(
170        &left_integer_digits,
171        &left_fractional_digits,
172        &right_integer_digits,
173        &right_fractional_digits,
174    );
175    if left_is_negative && !left_is_zero && right_is_negative && !right_is_zero {
176        return match non_negative_compare_result {
177            NumberCompareResult::Less => NumberCompareResult::Greater,
178            NumberCompareResult::Equal => NumberCompareResult::Equal,
179            NumberCompareResult::Greater => NumberCompareResult::Less,
180        };
181    }
182
183    non_negative_compare_result
184}
185
186impl Runtime {
187    /// Sub-goals inside numeric builtins: known env + builtin rules only.
188    /// Do not call [`Runtime::verify_non_equational_atomic_fact`] here: its forall / definition
189    /// round can recurse with outer goals (e.g. `b in R` for `0 <= a^b`, or order lemmas).
190    pub(crate) fn verify_non_equational_known_then_builtin_rules_only(
191        &mut self,
192        atomic_fact: &AtomicFact,
193        verify_state: &VerifyState,
194    ) -> Result<StmtResult, RuntimeError> {
195        let r = self.verify_non_equational_atomic_fact_with_known_atomic_facts(atomic_fact)?;
196        if r.is_true() {
197            return Ok(r);
198        }
199        self.verify_non_equational_atomic_fact_with_builtin_rules(atomic_fact, verify_state)
200    }
201
202    fn verify_zero_order_on_sub_expr(
203        &mut self,
204        zero: &Obj,
205        sub_expr: &Obj,
206        weak: bool,
207        line_file: &LineFile,
208    ) -> Result<StmtResult, RuntimeError> {
209        let fact: AtomicFact = if weak {
210            LessEqualFact::new(zero.clone(), sub_expr.clone(), line_file.clone()).into()
211        } else {
212            LessFact::new(zero.clone(), sub_expr.clone(), line_file.clone()).into()
213        };
214        let mut result = self.verify_non_equational_atomic_fact_with_known_atomic_facts(&fact)?;
215        if !result.is_true() {
216            result = self.verify_order_atomic_fact_numeric_builtin_only(&fact)?;
217        }
218        Ok(result)
219    }
220
221    /// `n >= 0` / `0 <= n` from known `n $in N` (e.g. `forall n N:` domain).
222    fn try_verify_order_nonnegative_from_membership_in_n(
223        &mut self,
224        atomic_fact: &AtomicFact,
225        verify_state: &VerifyState,
226    ) -> Result<Option<StmtResult>, RuntimeError> {
227        let (n, line_file) = match atomic_fact {
228            AtomicFact::GreaterEqualFact(f) => {
229                let Some(z) = self.resolve_obj_to_number(&f.right) else {
230                    return Ok(None);
231                };
232                if !matches!(
233                    compare_normalized_number_str_to_zero(&z.normalized_value),
234                    NumberCompareResult::Equal
235                ) {
236                    return Ok(None);
237                }
238                (f.left.clone(), f.line_file.clone())
239            }
240            AtomicFact::LessEqualFact(f) => {
241                let Some(z) = self.resolve_obj_to_number(&f.left) else {
242                    return Ok(None);
243                };
244                if !matches!(
245                    compare_normalized_number_str_to_zero(&z.normalized_value),
246                    NumberCompareResult::Equal
247                ) {
248                    return Ok(None);
249                }
250                (f.right.clone(), f.line_file.clone())
251            }
252            _ => return Ok(None),
253        };
254        let in_n: AtomicFact = InFact::new(n, StandardSet::N.into(), line_file.clone()).into();
255        if self
256            .verify_non_equational_known_then_builtin_rules_only(&in_n, verify_state)?
257            .is_true()
258        {
259            return Ok(Some(StmtResult::FactualStmtSuccess(
260                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
261                    atomic_fact.clone().into(),
262                    "n >= 0 from n $in N".to_string(),
263                    Vec::new(),
264                ),
265            )));
266        }
267        Ok(None)
268    }
269
270    fn verify_zero_le_abs_builtin_rule(
271        &mut self,
272        atomic_fact: &AtomicFact,
273    ) -> Result<Option<StmtResult>, RuntimeError> {
274        let Some(norm) = normalize_positive_order_atomic_fact(atomic_fact) else {
275            return Ok(None);
276        };
277        let AtomicFact::LessEqualFact(f) = &norm else {
278            return Ok(None);
279        };
280        if f.left.to_string() != "0" {
281            return Ok(None);
282        }
283        if !matches!(&f.right, Obj::Abs(_)) {
284            return Ok(None);
285        }
286        Ok(Some(StmtResult::FactualStmtSuccess(
287            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
288                atomic_fact.clone().into(),
289                "0 <= abs(x) for x in R".to_string(),
290                Vec::new(),
291            ),
292        )))
293    }
294
295    // `(-1)*x` order vs 0: e.g. `x < 0` or `x <= 0` implies `(-1)*x >= 0`; `x > 0` implies `(-1)*x < 0`.
296    // Also handles `0 <= (-1)*x` (equivalently `0 <= -x` when `-x` parses as `(-1)*x`).
297    fn try_verify_order_opposite_sign_mul_minus_one(
298        &mut self,
299        atomic_fact: &AtomicFact,
300        verify_state: &VerifyState,
301    ) -> Result<Option<StmtResult>, RuntimeError> {
302        let z: Obj = Number::new("0".to_string()).into();
303        let success = |msg: &'static str| {
304            Ok(Some(StmtResult::FactualStmtSuccess(
305                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
306                    atomic_fact.clone().into(),
307                    msg.to_string(),
308                    Vec::new(),
309                ),
310            )))
311        };
312        match atomic_fact {
313            AtomicFact::GreaterEqualFact(f) if self.obj_is_resolved_zero(&f.right) => {
314                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.left) {
315                    let le: AtomicFact =
316                        LessEqualFact::new(x.clone(), z.clone(), f.line_file.clone()).into();
317                    if self
318                        .verify_non_equational_known_then_builtin_rules_only(&le, verify_state)?
319                        .is_true()
320                    {
321                        return success("order: (-1)*x >= 0 from x <= 0");
322                    }
323                    let lt: AtomicFact = LessFact::new(x, z.clone(), f.line_file.clone()).into();
324                    if self
325                        .verify_non_equational_known_then_builtin_rules_only(&lt, verify_state)?
326                        .is_true()
327                    {
328                        return success("order: (-1)*x >= 0 from x < 0");
329                    }
330                }
331                Ok(None)
332            }
333            AtomicFact::GreaterFact(f) if self.obj_is_resolved_zero(&f.right) => {
334                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.left) {
335                    let lt: AtomicFact = LessFact::new(x, z.clone(), f.line_file.clone()).into();
336                    if self
337                        .verify_non_equational_known_then_builtin_rules_only(&lt, verify_state)?
338                        .is_true()
339                    {
340                        return success("order: (-1)*x > 0 from x < 0");
341                    }
342                }
343                Ok(None)
344            }
345            AtomicFact::LessEqualFact(f) if self.obj_is_resolved_zero(&f.right) => {
346                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.left) {
347                    let ge: AtomicFact =
348                        GreaterEqualFact::new(x.clone(), z.clone(), f.line_file.clone()).into();
349                    if self
350                        .verify_non_equational_known_then_builtin_rules_only(&ge, verify_state)?
351                        .is_true()
352                    {
353                        return success("order: (-1)*x <= 0 from x >= 0");
354                    }
355                    let gt: AtomicFact = GreaterFact::new(x, z.clone(), f.line_file.clone()).into();
356                    if self
357                        .verify_non_equational_known_then_builtin_rules_only(&gt, verify_state)?
358                        .is_true()
359                    {
360                        return success("order: (-1)*x <= 0 from x > 0");
361                    }
362                }
363                Ok(None)
364            }
365            AtomicFact::LessFact(f) if self.obj_is_resolved_zero(&f.right) => {
366                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.left) {
367                    let gt: AtomicFact = GreaterFact::new(x, z.clone(), f.line_file.clone()).into();
368                    if self
369                        .verify_non_equational_known_then_builtin_rules_only(&gt, verify_state)?
370                        .is_true()
371                    {
372                        return success("order: (-1)*x < 0 from x > 0");
373                    }
374                }
375                Ok(None)
376            }
377            AtomicFact::LessEqualFact(f) if self.obj_is_resolved_zero(&f.left) => {
378                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.right) {
379                    let le: AtomicFact =
380                        LessEqualFact::new(x.clone(), z.clone(), f.line_file.clone()).into();
381                    if self
382                        .verify_non_equational_known_then_builtin_rules_only(&le, verify_state)?
383                        .is_true()
384                    {
385                        return success("order: 0 <= (-1)*x from x <= 0");
386                    }
387                    let lt: AtomicFact = LessFact::new(x, z.clone(), f.line_file.clone()).into();
388                    if self
389                        .verify_non_equational_known_then_builtin_rules_only(&lt, verify_state)?
390                        .is_true()
391                    {
392                        return success("order: 0 <= (-1)*x from x < 0");
393                    }
394                }
395                Ok(None)
396            }
397            AtomicFact::LessFact(f) if self.obj_is_resolved_zero(&f.left) => {
398                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.right) {
399                    let lt: AtomicFact = LessFact::new(x, z.clone(), f.line_file.clone()).into();
400                    if self
401                        .verify_non_equational_known_then_builtin_rules_only(&lt, verify_state)?
402                        .is_true()
403                    {
404                        return success("order: 0 < (-1)*x from x < 0");
405                    }
406                }
407                Ok(None)
408            }
409            AtomicFact::GreaterEqualFact(f) if self.obj_is_resolved_zero(&f.left) => {
410                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.right) {
411                    let ge: AtomicFact =
412                        GreaterEqualFact::new(x.clone(), z.clone(), f.line_file.clone()).into();
413                    if self
414                        .verify_non_equational_known_then_builtin_rules_only(&ge, verify_state)?
415                        .is_true()
416                    {
417                        return success("order: 0 >= (-1)*x from x >= 0");
418                    }
419                    let gt: AtomicFact = GreaterFact::new(x, z.clone(), f.line_file.clone()).into();
420                    if self
421                        .verify_non_equational_known_then_builtin_rules_only(&gt, verify_state)?
422                        .is_true()
423                    {
424                        return success("order: 0 >= (-1)*x from x > 0");
425                    }
426                }
427                Ok(None)
428            }
429            AtomicFact::GreaterFact(f) if self.obj_is_resolved_zero(&f.left) => {
430                if let Some(x) = self.peel_mul_by_literal_neg_one(&f.right) {
431                    let gt: AtomicFact = GreaterFact::new(x, z.clone(), f.line_file.clone()).into();
432                    if self
433                        .verify_non_equational_known_then_builtin_rules_only(&gt, verify_state)?
434                        .is_true()
435                    {
436                        return success("order: 0 > (-1)*x from x > 0");
437                    }
438                }
439                Ok(None)
440            }
441            _ => Ok(None),
442        }
443    }
444
445    // Lit `know` facts for the nonnegative / positive cone under field operations used to live in
446    // `BUILTIN_ENV_CODE_FOR_FUNDAMENTAL_COMPARISON` (`fundamental_comparison.rs`). Those fragments
447    // were removed as redundant; the same mathematics is checked here on normalized `0 <=` / `0 <`
448    // goals (possibly after `normalize_positive_order_atomic_fact`):
449    // - Chained `+`: `0 <= a + b + …` from `0 <=` on each peeled summand; `0 < a + b + …` from
450    //   `(0 < a ∧ 0 <= b) ∨ (0 <= a ∧ 0 < b)` at each binary `+`.
451    // - Powers: literal even integer exponent ⇒ `0 <= base^n`; literal integer exponent and `0 <= base`
452    //   (or `0 < base` if exponent < 0) ⇒ `0 <= base^n`; `a * a` with equal factors; `0 < base^exp`
453    //   from `0 < base` and `exp in R`.
454    // - Products and quotients: `0 <= a * b`, `0 < a * b`, `0 <= a / b` (denominator strictly
455    //   positive), `0 < a / b`, each with recursive sub-goals on operands.
456    // The Lit environment still records order via differences (`a <= b` iff `0 <= b - a`, etc.) and
457    // `a != 0 ⇒ 0 < a^2` (strict square). This path also bridges `0 <= u - v` / `0 < u - v` to `v <= u` / `v < u`.
458    // Algebraic closure (+, -, *, /) on general `a <= b` / `a < b` is in `order_algebra_builtin.rs`.
459    pub fn verify_order_atomic_fact_numeric_builtin_only(
460        &mut self,
461        atomic_fact: &AtomicFact,
462    ) -> Result<StmtResult, RuntimeError> {
463        let vs = VerifyState::new(0, true);
464        if let Some(result) =
465            self.try_verify_order_nonnegative_from_membership_in_n(atomic_fact, &vs)?
466        {
467            return Ok(result);
468        }
469        if let Some(result) = self.try_verify_order_opposite_sign_mul_minus_one(atomic_fact, &vs)? {
470            return Ok(result);
471        }
472        if let Some(result) = self.try_verify_order_two_sums_same_index_and_bounds(atomic_fact)? {
473            return Ok(result);
474        }
475        if let Some(result) = self.try_verify_zero_order_sum_by_pointwise_summand_order(atomic_fact)? {
476            return Ok(result);
477        }
478        if let Some(result) = self.verify_order_from_known_negated_complement(atomic_fact)? {
479            return Ok(result);
480        }
481        if let Some(result) = self.verify_negated_order_from_known_equivalent_order(atomic_fact)? {
482            return Ok(result);
483        }
484        if let Some(result) = self.verify_order_algebra_structural_builtin_rule(atomic_fact)? {
485            return Ok(result);
486        }
487        if let Some(result) = self.verify_zero_le_abs_builtin_rule(atomic_fact)? {
488            return Ok(result);
489        }
490        if let Some(result) =
491            self.verify_zero_order_on_sub_from_two_sided_order_builtin_rule(atomic_fact)?
492        {
493            return Ok(result);
494        }
495        if let Some(result) =
496            self.verify_zero_le_add_from_known_atomic_facts_builtin_rule(atomic_fact)?
497        {
498            return Ok(result);
499        }
500        if let Some(result) =
501            self.verify_zero_lt_add_from_known_atomic_facts_builtin_rule(atomic_fact)?
502        {
503            return Ok(result);
504        }
505        if let Some(result) = self.verify_zero_le_even_integer_pow_builtin_rule(atomic_fact)? {
506            return Ok(result);
507        }
508        if let Some(result) =
509            self.verify_zero_lt_even_integer_pow_from_base_nonzero_builtin_rule(atomic_fact)?
510        {
511            return Ok(result);
512        }
513        if let Some(result) =
514            self.verify_zero_lt_pow_from_positive_base_real_exp_builtin_rule(atomic_fact)?
515        {
516            return Ok(result);
517        }
518        if let Some(result) =
519            self.verify_zero_le_pow_from_positive_base_real_exp_builtin_rule(atomic_fact)?
520        {
521            return Ok(result);
522        }
523        if let Some(result) =
524            self.verify_zero_le_pow_integer_exponent_from_nonneg_base_builtin_rule(atomic_fact)?
525        {
526            return Ok(result);
527        }
528        if let Some(result) =
529            self.verify_zero_le_mul_from_known_atomic_facts_builtin_rule(atomic_fact)?
530        {
531            return Ok(result);
532        }
533        if let Some(result) =
534            self.verify_zero_lt_mul_from_known_atomic_facts_builtin_rule(atomic_fact)?
535        {
536            return Ok(result);
537        }
538        if let Some(result) =
539            self.verify_zero_le_div_from_known_atomic_facts_builtin_rule(atomic_fact)?
540        {
541            return Ok(result);
542        }
543        if let Some(result) =
544            self.verify_zero_lt_div_from_known_atomic_facts_builtin_rule(atomic_fact)?
545        {
546            return Ok(result);
547        }
548
549        if let AtomicFact::LessEqualFact(less_equal_fact) = atomic_fact {
550            if less_equal_fact.left.to_string() == less_equal_fact.right.to_string() {
551                return Ok(StmtResult::FactualStmtSuccess(
552                    FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
553                        less_equal_fact.clone().into(),
554                        "less_equal_fact_equal".to_string(),
555                        Vec::new(),
556                    ),
557                ));
558            }
559            let strict_fact: Fact = LessFact::new(
560                less_equal_fact.left.clone(),
561                less_equal_fact.right.clone(),
562                less_equal_fact.line_file.clone(),
563            )
564            .into();
565            let strict_key = strict_fact.to_string();
566            let (cache_ok, cache_line_file) = self.cache_known_facts_contains(&strict_key);
567            if cache_ok {
568                return Ok(StmtResult::FactualStmtSuccess(
569                    FactualStmtSuccess::new_with_verified_by_known_fact_source_recording_facts(
570                        less_equal_fact.clone().into(),
571                        strict_key,
572                        Some(strict_fact),
573                        Some(cache_line_file),
574                        Vec::new(),
575                    ),
576                ));
577            }
578        }
579        if let AtomicFact::GreaterEqualFact(greater_equal_fact) = atomic_fact {
580            if greater_equal_fact.left.to_string() == greater_equal_fact.right.to_string() {
581                return Ok(StmtResult::FactualStmtSuccess(
582                    FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
583                        greater_equal_fact.clone().into(),
584                        "greater_equal_fact_equal".to_string(),
585                        Vec::new(),
586                    ),
587                ));
588            }
589        }
590        if let Some(true) = self.verify_number_comparison_builtin_rule(atomic_fact) {
591            Ok(StmtResult::FactualStmtSuccess(
592                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
593                    atomic_fact.clone().into(),
594                    "number comparison".to_string(),
595                    Vec::new(),
596                ),
597            ))
598        } else {
599            Ok(StmtResult::StmtUnknown(StmtUnknown::new()))
600        }
601    }
602
603    // `a > b` from known `not (a <= b)`, `a < b` from `not (a >= b)`, etc. (total order duality).
604    fn verify_order_from_known_negated_complement(
605        &mut self,
606        atomic_fact: &AtomicFact,
607    ) -> Result<Option<StmtResult>, RuntimeError> {
608        let negated: Option<AtomicFact> = match atomic_fact {
609            AtomicFact::GreaterFact(f) => {
610                Some(NotLessEqualFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into())
611            }
612            AtomicFact::LessFact(f) => Some(
613                NotGreaterEqualFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into(),
614            ),
615            AtomicFact::GreaterEqualFact(f) => {
616                Some(NotLessFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into())
617            }
618            AtomicFact::LessEqualFact(f) => {
619                Some(NotGreaterFact::new(f.left.clone(), f.right.clone(), f.line_file.clone()).into())
620            }
621            _ => None,
622        };
623        let Some(neg) = negated else {
624            return Ok(None);
625        };
626        let sub = self.verify_non_equational_atomic_fact_with_known_atomic_facts(&neg)?;
627        if sub.is_true() {
628            return Ok(Some(
629                FactualStmtSuccess::new_with_verified_by_builtin_rules(
630                    atomic_fact.clone().into(),
631                    InferResult::new(),
632                    "order_from_known_negated_complement".to_string(),
633                    vec![sub],
634                )
635                .into(),
636            ));
637        }
638        Ok(None)
639    }
640
641    // `not (a < b)` etc.: only consult known atomic facts for the equivalent weak/strict order.
642    fn verify_negated_order_from_known_equivalent_order(
643        &mut self,
644        atomic_fact: &AtomicFact,
645    ) -> Result<Option<StmtResult>, RuntimeError> {
646        let candidates: Vec<AtomicFact> = match atomic_fact {
647            AtomicFact::NotLessFact(f) => {
648                let lf = f.line_file.clone();
649                vec![
650                    LessEqualFact::new(f.right.clone(), f.left.clone(), lf.clone()).into(),
651                    GreaterEqualFact::new(f.left.clone(), f.right.clone(), lf).into(),
652                ]
653            }
654            AtomicFact::NotGreaterFact(f) => {
655                let lf = f.line_file.clone();
656                vec![
657                    LessEqualFact::new(f.left.clone(), f.right.clone(), lf.clone()).into(),
658                    GreaterEqualFact::new(f.right.clone(), f.left.clone(), lf).into(),
659                ]
660            }
661            AtomicFact::NotLessEqualFact(f) => {
662                let lf = f.line_file.clone();
663                vec![
664                    LessFact::new(f.right.clone(), f.left.clone(), lf.clone()).into(),
665                    GreaterFact::new(f.left.clone(), f.right.clone(), lf).into(),
666                ]
667            }
668            AtomicFact::NotGreaterEqualFact(f) => {
669                let lf = f.line_file.clone();
670                vec![
671                    LessFact::new(f.left.clone(), f.right.clone(), lf.clone()).into(),
672                    GreaterFact::new(f.right.clone(), f.left.clone(), lf).into(),
673                ]
674            }
675            _ => return Ok(None),
676        };
677        for candidate in candidates {
678            let sub = self.verify_non_equational_atomic_fact_with_known_atomic_facts(&candidate)?;
679            if sub.is_true() {
680                return Ok(Some(
681                    FactualStmtSuccess::new_with_verified_by_builtin_rules(
682                        atomic_fact.clone().into(),
683                        InferResult::new(),
684                        "negated_order_from_known_equivalent_order".to_string(),
685                        vec![sub],
686                    )
687                    .into(),
688                ));
689            }
690        }
691        Ok(None)
692    }
693
694    // Matches Lit `a <= b` <=> `0 <= b - a` (and strict): `0 <= u - v` iff `v <= u`, `0 < u - v` iff `v < u`.
695    fn verify_zero_order_on_sub_from_two_sided_order_builtin_rule(
696        &mut self,
697        atomic_fact: &AtomicFact,
698    ) -> Result<Option<StmtResult>, RuntimeError> {
699        let Some(norm) = normalize_positive_order_atomic_fact(atomic_fact) else {
700            return Ok(None);
701        };
702        match &norm {
703            AtomicFact::LessEqualFact(f) if f.left.to_string() == "0" => {
704                let Obj::Sub(sub) = &f.right else {
705                    return Ok(None);
706                };
707                let derived: AtomicFact = LessEqualFact::new(
708                    sub.right.as_ref().clone(),
709                    sub.left.as_ref().clone(),
710                    f.line_file.clone(),
711                )
712                .into();
713                let mut result =
714                    self.verify_non_equational_atomic_fact_with_known_atomic_facts(&derived)?;
715                if !result.is_true() {
716                    result = self.verify_order_atomic_fact_numeric_builtin_only(&derived)?;
717                }
718                if result.is_true() {
719                    Ok(Some(StmtResult::FactualStmtSuccess(
720                        FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
721                            atomic_fact.clone().into(),
722                            "0 <= u - v from v <= u".to_string(),
723                            vec![result],
724                        ),
725                    )))
726                } else {
727                    Ok(None)
728                }
729            }
730            AtomicFact::LessFact(f) if f.left.to_string() == "0" => {
731                let Obj::Sub(sub) = &f.right else {
732                    return Ok(None);
733                };
734                let derived: AtomicFact = LessFact::new(
735                    sub.right.as_ref().clone(),
736                    sub.left.as_ref().clone(),
737                    f.line_file.clone(),
738                )
739                .into();
740                let mut result =
741                    self.verify_non_equational_atomic_fact_with_known_atomic_facts(&derived)?;
742                if !result.is_true() {
743                    result = self.verify_order_atomic_fact_numeric_builtin_only(&derived)?;
744                }
745                if result.is_true() {
746                    Ok(Some(StmtResult::FactualStmtSuccess(
747                        FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
748                            atomic_fact.clone().into(),
749                            "0 < u - v from v < u".to_string(),
750                            vec![result],
751                        ),
752                    )))
753                } else {
754                    Ok(None)
755                }
756            }
757            _ => Ok(None),
758        }
759    }
760
761    fn verify_zero_le_add_from_known_atomic_facts_builtin_rule(
762        &mut self,
763        atomic_fact: &AtomicFact,
764    ) -> Result<Option<StmtResult>, RuntimeError> {
765        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
766            return Ok(None);
767        };
768        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
769            return Ok(None);
770        };
771        if less_equal_fact.left.to_string() != "0" {
772            return Ok(None);
773        }
774        let Obj::Add(add_obj) = &less_equal_fact.right else {
775            return Ok(None);
776        };
777
778        let zero = &less_equal_fact.left;
779        let line_file = &less_equal_fact.line_file;
780        let left_verify_result =
781            self.verify_zero_order_on_sub_expr(zero, add_obj.left.as_ref(), true, line_file)?;
782        if !left_verify_result.is_true() {
783            return Ok(None);
784        }
785        let right_verify_result =
786            self.verify_zero_order_on_sub_expr(zero, add_obj.right.as_ref(), true, line_file)?;
787        if !right_verify_result.is_true() {
788            return Ok(None);
789        }
790
791        Ok(Some(StmtResult::FactualStmtSuccess(
792            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
793                atomic_fact.clone().into(),
794                "0 <= a + b from known atomic facts 0 <= a and 0 <= b".to_string(),
795                vec![left_verify_result, right_verify_result],
796            ),
797        )))
798    }
799
800    fn verify_zero_lt_add_from_known_atomic_facts_builtin_rule(
801        &mut self,
802        atomic_fact: &AtomicFact,
803    ) -> Result<Option<StmtResult>, RuntimeError> {
804        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
805            return Ok(None);
806        };
807        let AtomicFact::LessFact(less_fact) = normalized_fact else {
808            return Ok(None);
809        };
810        if less_fact.left.to_string() != "0" {
811            return Ok(None);
812        }
813        let Obj::Add(add_obj) = &less_fact.right else {
814            return Ok(None);
815        };
816
817        let zero = &less_fact.left;
818        let line_file = &less_fact.line_file;
819
820        let strict_then_weak = |this: &mut Self| -> Result<Option<StmtResult>, RuntimeError> {
821            let left_result =
822                this.verify_zero_order_on_sub_expr(zero, add_obj.left.as_ref(), false, line_file)?;
823            if !left_result.is_true() {
824                return Ok(None);
825            }
826            let right_result =
827                this.verify_zero_order_on_sub_expr(zero, add_obj.right.as_ref(), true, line_file)?;
828            if !right_result.is_true() {
829                return Ok(None);
830            }
831            Ok(Some(StmtResult::FactualStmtSuccess(
832                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
833                    atomic_fact.clone().into(),
834                    "0 < a + b from (0 < a and 0 <= b)".to_string(),
835                    vec![left_result, right_result],
836                ),
837            )))
838        };
839        let weak_then_strict = |this: &mut Self| -> Result<Option<StmtResult>, RuntimeError> {
840            let left_result =
841                this.verify_zero_order_on_sub_expr(zero, add_obj.left.as_ref(), true, line_file)?;
842            if !left_result.is_true() {
843                return Ok(None);
844            }
845            let right_result =
846                this.verify_zero_order_on_sub_expr(zero, add_obj.right.as_ref(), false, line_file)?;
847            if !right_result.is_true() {
848                return Ok(None);
849            }
850            Ok(Some(StmtResult::FactualStmtSuccess(
851                FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
852                    atomic_fact.clone().into(),
853                    "0 < a + b from (0 <= a and 0 < b)".to_string(),
854                    vec![left_result, right_result],
855                ),
856            )))
857        };
858
859        if let Some(success) = strict_then_weak(self)? {
860            return Ok(Some(success));
861        }
862        weak_then_strict(self)
863    }
864
865    fn verify_zero_le_even_integer_pow_builtin_rule(
866        &mut self,
867        atomic_fact: &AtomicFact,
868    ) -> Result<Option<StmtResult>, RuntimeError> {
869        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
870            return Ok(None);
871        };
872        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
873            return Ok(None);
874        };
875        if less_equal_fact.left.to_string() != "0" {
876            return Ok(None);
877        }
878        let right = &less_equal_fact.right;
879        let is_equal_factors_mul = match right {
880            Obj::Mul(mul_obj) => mul_obj.left.to_string() == mul_obj.right.to_string(),
881            _ => false,
882        };
883        let is_even_pow = match right {
884            Obj::Pow(pow_obj) => match pow_obj.exponent.as_ref() {
885                Obj::Number(n) => normalized_decimal_string_is_even_integer(&n.normalized_value),
886                _ => false,
887            },
888            _ => false,
889        };
890        if !is_equal_factors_mul && !is_even_pow {
891            return Ok(None);
892        }
893        let msg = if is_equal_factors_mul {
894            "0 <= a * a from even integer exponent (here 2) (forall a R)".to_string()
895        } else {
896            "0 <= a^n for even integer n (forall a R)".to_string()
897        };
898        Ok(Some(StmtResult::FactualStmtSuccess(
899            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
900                atomic_fact.clone().into(),
901                msg,
902                Vec::new(),
903            ),
904        )))
905    }
906
907    fn verify_zero_lt_even_integer_pow_from_base_nonzero_builtin_rule(
908        &mut self,
909        atomic_fact: &AtomicFact,
910    ) -> Result<Option<StmtResult>, RuntimeError> {
911        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
912            return Ok(None);
913        };
914        let AtomicFact::LessFact(less_fact) = normalized_fact else {
915            return Ok(None);
916        };
917        if less_fact.left.to_string() != "0" {
918            return Ok(None);
919        }
920        let Obj::Pow(pow_obj) = &less_fact.right else {
921            return Ok(None);
922        };
923        let Obj::Number(exp_num) = pow_obj.exponent.as_ref() else {
924            return Ok(None);
925        };
926        if !normalized_decimal_string_is_even_integer(&exp_num.normalized_value) {
927            return Ok(None);
928        }
929
930        let line_file = less_fact.line_file.clone();
931        let base = pow_obj.base.as_ref().clone();
932        let zero_obj: Obj = Number::new("0".to_string()).into();
933        let base_neq_zero: AtomicFact = NotEqualFact::new(base, zero_obj, line_file.clone()).into();
934
935        let neq_result = self.verify_non_equational_known_then_builtin_rules_only(
936            &base_neq_zero,
937            &VerifyState::new(0, true),
938        )?;
939        if !neq_result.is_true() {
940            return Ok(None);
941        }
942
943        Ok(Some(StmtResult::FactualStmtSuccess(
944            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
945                atomic_fact.clone().into(),
946                "0 < a^n for even integer n from a != 0".to_string(),
947                vec![neq_result],
948            ),
949        )))
950    }
951
952    // Matches `0 < a^b` / `a^b > 0` when `0 < a` is proved (or known) and `b in R`.
953    fn verify_zero_lt_pow_from_positive_base_real_exp_builtin_rule(
954        &mut self,
955        atomic_fact: &AtomicFact,
956    ) -> Result<Option<StmtResult>, RuntimeError> {
957        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
958            return Ok(None);
959        };
960        let AtomicFact::LessFact(less_fact) = normalized_fact else {
961            return Ok(None);
962        };
963        if less_fact.left.to_string() != "0" {
964            return Ok(None);
965        }
966        let Obj::Pow(pow_obj) = &less_fact.right else {
967            return Ok(None);
968        };
969        let zero = &less_fact.left;
970        let line_file = &less_fact.line_file;
971        let base = pow_obj.base.as_ref();
972        let base_result = self.verify_zero_order_on_sub_expr(zero, base, false, line_file)?;
973        if !base_result.is_true() {
974            return Ok(None);
975        }
976        let in_r: AtomicFact = InFact::new(
977            (*pow_obj.exponent).clone(),
978            StandardSet::R.into(),
979            line_file.clone(),
980        )
981        .into();
982        let in_r_result = self.verify_non_equational_known_then_builtin_rules_only(
983            &in_r,
984            &VerifyState::new(0, true),
985        )?;
986        if !in_r_result.is_true() {
987            return Ok(None);
988        }
989        Ok(Some(StmtResult::FactualStmtSuccess(
990            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
991                atomic_fact.clone().into(),
992                "0 < a^b from 0 < a and b in R".to_string(),
993                vec![base_result, in_r_result],
994            ),
995        )))
996    }
997
998    // `0 <= a^b` / `a^b >= 0` with the same premises as strict `0 < a^b`: `0 < a` and `b in R`.
999    // Covers symbolic exponents (e.g. `2^m`) where the literal-exponent `0 <= a^n` rule does not apply.
1000    fn verify_zero_le_pow_from_positive_base_real_exp_builtin_rule(
1001        &mut self,
1002        atomic_fact: &AtomicFact,
1003    ) -> Result<Option<StmtResult>, RuntimeError> {
1004        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1005            return Ok(None);
1006        };
1007        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
1008            return Ok(None);
1009        };
1010        if less_equal_fact.left.to_string() != "0" {
1011            return Ok(None);
1012        }
1013        let Obj::Pow(pow_obj) = &less_equal_fact.right else {
1014            return Ok(None);
1015        };
1016        let zero = &less_equal_fact.left;
1017        let line_file = &less_equal_fact.line_file;
1018        let base = pow_obj.base.as_ref();
1019        let base_result = self.verify_zero_order_on_sub_expr(zero, base, false, line_file)?;
1020        if !base_result.is_true() {
1021            return Ok(None);
1022        }
1023        let in_r: AtomicFact = InFact::new(
1024            (*pow_obj.exponent).clone(),
1025            StandardSet::R.into(),
1026            line_file.clone(),
1027        )
1028        .into();
1029        let in_r_result = self.verify_non_equational_known_then_builtin_rules_only(
1030            &in_r,
1031            &VerifyState::new(0, true),
1032        )?;
1033        if !in_r_result.is_true() {
1034            return Ok(None);
1035        }
1036        Ok(Some(StmtResult::FactualStmtSuccess(
1037            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1038                atomic_fact.clone().into(),
1039                "0 <= a^b from 0 < a and b in R".to_string(),
1040                vec![base_result, in_r_result],
1041            ),
1042        )))
1043    }
1044
1045    fn verify_zero_le_pow_integer_exponent_from_nonneg_base_builtin_rule(
1046        &mut self,
1047        atomic_fact: &AtomicFact,
1048    ) -> Result<Option<StmtResult>, RuntimeError> {
1049        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1050            return Ok(None);
1051        };
1052        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
1053            return Ok(None);
1054        };
1055        if less_equal_fact.left.to_string() != "0" {
1056            return Ok(None);
1057        }
1058        let Obj::Pow(pow_obj) = &less_equal_fact.right else {
1059            return Ok(None);
1060        };
1061        let Obj::Number(exp_num) = pow_obj.exponent.as_ref() else {
1062            return Ok(None);
1063        };
1064        if !normalized_decimal_string_is_integer(&exp_num.normalized_value) {
1065            return Ok(None);
1066        }
1067
1068        let zero = &less_equal_fact.left;
1069        let line_file = &less_equal_fact.line_file;
1070        let base = pow_obj.base.as_ref();
1071
1072        let exponent_vs_zero = compare_normalized_number_str_to_zero(&exp_num.normalized_value);
1073        let base_result = match exponent_vs_zero {
1074            NumberCompareResult::Less => {
1075                self.verify_zero_order_on_sub_expr(zero, base, false, line_file)?
1076            }
1077            NumberCompareResult::Equal | NumberCompareResult::Greater => {
1078                self.verify_zero_order_on_sub_expr(zero, base, true, line_file)?
1079            }
1080        };
1081        if !base_result.is_true() {
1082            return Ok(None);
1083        }
1084
1085        let msg = match exponent_vs_zero {
1086            NumberCompareResult::Less => "0 <= a^n from 0 < a and integer n < 0".to_string(),
1087            _ => "0 <= a^n from 0 <= a and integer n".to_string(),
1088        };
1089
1090        Ok(Some(StmtResult::FactualStmtSuccess(
1091            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1092                atomic_fact.clone().into(),
1093                msg,
1094                vec![base_result],
1095            ),
1096        )))
1097    }
1098
1099    fn verify_zero_le_mul_from_known_atomic_facts_builtin_rule(
1100        &mut self,
1101        atomic_fact: &AtomicFact,
1102    ) -> Result<Option<StmtResult>, RuntimeError> {
1103        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1104            return Ok(None);
1105        };
1106        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
1107            return Ok(None);
1108        };
1109        if less_equal_fact.left.to_string() != "0" {
1110            return Ok(None);
1111        }
1112        let Obj::Mul(mul_obj) = &less_equal_fact.right else {
1113            return Ok(None);
1114        };
1115
1116        let zero = &less_equal_fact.left;
1117        let line_file = &less_equal_fact.line_file;
1118        let left_verify_result =
1119            self.verify_zero_order_on_sub_expr(zero, mul_obj.left.as_ref(), true, line_file)?;
1120        if !left_verify_result.is_true() {
1121            return Ok(None);
1122        }
1123        let right_verify_result =
1124            self.verify_zero_order_on_sub_expr(zero, mul_obj.right.as_ref(), true, line_file)?;
1125        if !right_verify_result.is_true() {
1126            return Ok(None);
1127        }
1128
1129        Ok(Some(StmtResult::FactualStmtSuccess(
1130            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1131                atomic_fact.clone().into(),
1132                "0 <= a * b from 0 <= a and 0 <= b".to_string(),
1133                vec![left_verify_result, right_verify_result],
1134            ),
1135        )))
1136    }
1137
1138    fn verify_zero_lt_mul_from_known_atomic_facts_builtin_rule(
1139        &mut self,
1140        atomic_fact: &AtomicFact,
1141    ) -> Result<Option<StmtResult>, RuntimeError> {
1142        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1143            return Ok(None);
1144        };
1145        let AtomicFact::LessFact(less_fact) = normalized_fact else {
1146            return Ok(None);
1147        };
1148        if less_fact.left.to_string() != "0" {
1149            return Ok(None);
1150        }
1151        let Obj::Mul(mul_obj) = &less_fact.right else {
1152            return Ok(None);
1153        };
1154
1155        let zero = &less_fact.left;
1156        let line_file = &less_fact.line_file;
1157        let left_verify_result =
1158            self.verify_zero_order_on_sub_expr(zero, mul_obj.left.as_ref(), false, line_file)?;
1159        if !left_verify_result.is_true() {
1160            return Ok(None);
1161        }
1162        let right_verify_result =
1163            self.verify_zero_order_on_sub_expr(zero, mul_obj.right.as_ref(), false, line_file)?;
1164        if !right_verify_result.is_true() {
1165            return Ok(None);
1166        }
1167
1168        Ok(Some(StmtResult::FactualStmtSuccess(
1169            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1170                atomic_fact.clone().into(),
1171                "0 < a * b from 0 < a and 0 < b".to_string(),
1172                vec![left_verify_result, right_verify_result],
1173            ),
1174        )))
1175    }
1176
1177    fn verify_zero_le_div_from_known_atomic_facts_builtin_rule(
1178        &mut self,
1179        atomic_fact: &AtomicFact,
1180    ) -> Result<Option<StmtResult>, RuntimeError> {
1181        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1182            return Ok(None);
1183        };
1184        let AtomicFact::LessEqualFact(less_equal_fact) = normalized_fact else {
1185            return Ok(None);
1186        };
1187        if less_equal_fact.left.to_string() != "0" {
1188            return Ok(None);
1189        }
1190        let Obj::Div(div_obj) = &less_equal_fact.right else {
1191            return Ok(None);
1192        };
1193
1194        let zero = &less_equal_fact.left;
1195        let line_file = &less_equal_fact.line_file;
1196        let numer_result =
1197            self.verify_zero_order_on_sub_expr(zero, div_obj.left.as_ref(), true, line_file)?;
1198        if !numer_result.is_true() {
1199            return Ok(None);
1200        }
1201        let denom_result =
1202            self.verify_zero_order_on_sub_expr(zero, div_obj.right.as_ref(), false, line_file)?;
1203        if !denom_result.is_true() {
1204            return Ok(None);
1205        }
1206
1207        Ok(Some(StmtResult::FactualStmtSuccess(
1208            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1209                atomic_fact.clone().into(),
1210                "0 <= a / b from 0 <= a and 0 < b".to_string(),
1211                vec![numer_result, denom_result],
1212            ),
1213        )))
1214    }
1215
1216    fn verify_zero_lt_div_from_known_atomic_facts_builtin_rule(
1217        &mut self,
1218        atomic_fact: &AtomicFact,
1219    ) -> Result<Option<StmtResult>, RuntimeError> {
1220        let Some(normalized_fact) = normalize_positive_order_atomic_fact(atomic_fact) else {
1221            return Ok(None);
1222        };
1223        let AtomicFact::LessFact(less_fact) = normalized_fact else {
1224            return Ok(None);
1225        };
1226        if less_fact.left.to_string() != "0" {
1227            return Ok(None);
1228        }
1229        let Obj::Div(div_obj) = &less_fact.right else {
1230            return Ok(None);
1231        };
1232
1233        let zero = &less_fact.left;
1234        let line_file = &less_fact.line_file;
1235        let numer_result =
1236            self.verify_zero_order_on_sub_expr(zero, div_obj.left.as_ref(), false, line_file)?;
1237        if !numer_result.is_true() {
1238            return Ok(None);
1239        }
1240        let denom_result =
1241            self.verify_zero_order_on_sub_expr(zero, div_obj.right.as_ref(), false, line_file)?;
1242        if !denom_result.is_true() {
1243            return Ok(None);
1244        }
1245
1246        Ok(Some(StmtResult::FactualStmtSuccess(
1247            FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
1248                atomic_fact.clone().into(),
1249                "0 < a / b from 0 < a and 0 < b".to_string(),
1250                vec![numer_result, denom_result],
1251            ),
1252        )))
1253    }
1254
1255    pub fn verify_number_comparison_builtin_rule(&self, atomic_fact: &AtomicFact) -> Option<bool> {
1256        let normalized = normalize_positive_order_atomic_fact(atomic_fact)?;
1257        match normalized {
1258            AtomicFact::LessFact(less_fact) => {
1259                if let Some(calculated_number_string_pair) =
1260                    self.calculate_obj_pair_to_number_strings(&less_fact.left, &less_fact.right)
1261                {
1262                    return Some(matches!(
1263                        compare_number_strings(
1264                            &calculated_number_string_pair.0,
1265                            &calculated_number_string_pair.1
1266                        ),
1267                        NumberCompareResult::Less
1268                    ));
1269                }
1270                self.try_verify_numeric_order_via_div_elimination(
1271                    &less_fact.left,
1272                    &less_fact.right,
1273                    false,
1274                )
1275            }
1276            AtomicFact::LessEqualFact(less_equal_fact) => {
1277                if let Some(calculated_number_string_pair) = self
1278                    .calculate_obj_pair_to_number_strings(
1279                        &less_equal_fact.left,
1280                        &less_equal_fact.right,
1281                    )
1282                {
1283                    let compare_result = compare_number_strings(
1284                        &calculated_number_string_pair.0,
1285                        &calculated_number_string_pair.1,
1286                    );
1287                    return Some(matches!(
1288                        compare_result,
1289                        NumberCompareResult::Less | NumberCompareResult::Equal
1290                    ));
1291                }
1292                self.try_verify_numeric_order_via_div_elimination(
1293                    &less_equal_fact.left,
1294                    &less_equal_fact.right,
1295                    true,
1296                )
1297            }
1298            _ => None,
1299        }
1300    }
1301
1302    fn calculate_obj_pair_to_number_strings(
1303        &self,
1304        left_obj: &Obj,
1305        right_obj: &Obj,
1306    ) -> Option<(String, String)> {
1307        let left_number = self.resolve_obj_to_number_resolved(left_obj)?;
1308        let right_number = self.resolve_obj_to_number_resolved(right_obj)?;
1309        Some((left_number.normalized_value, right_number.normalized_value))
1310    }
1311}