From f11d27377d785d054b7f48643429d2a8d11e68a7 Mon Sep 17 00:00:00 2001 From: "Claude (numtower)" Date: Fri, 9 Oct 2026 00:14:57 -0700 Subject: [PATCH] test(numtower): property tests run at the 256-case budget (improvise-59nw.1) Answers Copilot on PR #337 (context/design-principles.md section 7). Every fault in the review's table still fails by name at 256 cases. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze --- context/test-evidence/numtower.md | 21 +++++++++++++++------ crates/numtower/tests/properties.rs | 10 +++++----- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/context/test-evidence/numtower.md b/context/test-evidence/numtower.md index 451f5a0c..c57949a3 100644 --- a/context/test-evidence/numtower.md +++ b/context/test-evidence/numtower.md @@ -105,11 +105,18 @@ to discriminate. ### rust_decimal_to_f64_agrees_on_fifteen_digit_mantissas_so_a_narrow_generator_is_vacuous -This test **passes the rival** on purpose. With mantissas of at most 15 digits, 4,096 -cases, `rust_decimal`'s `to_f64` agrees with the text every time, as ADR 005 says (200,000 -of 200,000). So a test whose generator stays in that range proves nothing about the -conversion. This test is why the generator above spans 96 bits, and it is what fails, and -should be re-read, if `rust_decimal` ever fixes its conversion. +This test **passes the rival** on purpose. With mantissas of at most 15 digits, +`rust_decimal`'s `to_f64` agrees with the text every time, at this test's 256 cases as +in ADR 005's 200,000 of 200,000. So a test whose generator stays in that range proves +nothing about the conversion, and this test is why the generator above spans 96 bits. It +ran at 4,096 cases until Copilot pointed out, on PR #337, that the repository's budget is +256 (`context/design-principles.md` ยง7). The 200,000 is the ADR's measurement, and this +test only pins its direction. + +If `rust_decimal` ever fixes its conversion, this test still passes. What fails then is +`the_f64_conversion_agrees_with_parsing_the_decimal_text_catches_rust_decimal_to_f64`, +whose rival would agree. That is the signal to re-read both. An earlier version of this +entry named this test instead, which was wrong. ### a_fractions_conversion_lies_inside_the_round_to_nearest_even_interval @@ -205,7 +212,9 @@ faults passed the whole suite: - F14: the past-K read dropped the decimal partition; - F15: the past-K read dropped every inexact term. -The test now has the reviewer's oracle and 512 cases. Half the lists come from the +The test now has the reviewer's oracle, at the repository's 256-case budget. 34ab0fc3 ran +it at 512, and Copilot pointed out the budget. At 256, every fault in the table below +still failed by name, and F7, F8, F12, F14 and F15 failed in four of four passes. Half the lists come from the fourteen-denominator pool, up to 40 terms long, so groups receive several terms and K is passed often. **It also counts how many cases reach each branch and fails if any branch is reached fewer than 20 times**, so a generator change cannot silently starve one. diff --git a/crates/numtower/tests/properties.rs b/crates/numtower/tests/properties.rs index b6b42e18..5c5a8e5e 100644 --- a/crates/numtower/tests/properties.rs +++ b/crates/numtower/tests/properties.rs @@ -93,7 +93,7 @@ fn an_exact_result_is_never_a_rounded_one_catches_a_silently_rounding_add() { fn check_contagion(add: BinOp, deterministic: bool) -> Result<(), String> { let seed = Just((dec("2"), Num::inexact(3.0))); let cases = prop_oneof![1 => seed, 8 => (any_num(), any_num())]; - run(cases, 512, deterministic, |(a, b)| { + run(cases, 256, deterministic, |(a, b)| { let results = [ ("+", add(&a, &b)), ("-", Some(&a - &b)), @@ -310,7 +310,7 @@ fn the_f64_conversion_agrees_with_parsing_the_decimal_text_catches_rust_decimal_ #[test] fn rust_decimal_to_f64_agrees_on_fifteen_digit_mantissas_so_a_narrow_generator_is_vacuous() { let narrow = 10i128.pow(15) - 1; - let result = run((-narrow..=narrow, 0u32..=15), 4096, true, |(m, s)| { + let result = run((-narrow..=narrow, 0u32..=15), 256, true, |(m, s)| { let d = Decimal::from_i128_with_scale(m, s); prop_assert_eq!( crate_to_f64(&d).to_bits(), @@ -772,7 +772,7 @@ fn the_accumulator_reads_the_exact_sum_or_its_stated_roundings() { prop::collection::vec(weighted(term()), 0..24), prop::collection::vec(weighted(pooled_term()), 0..40), ]; - run(lists, 512, false, |xs| { + run(lists, 256, false, |xs| { let got = sum_weighted(&xs); let e = Expected::of(&xs); let present = e.inexact.values().any(|w| *w != 0); @@ -1004,7 +1004,7 @@ fn check_language( parse: &dyn Fn(&str, Typing) -> Option, deterministic: bool, ) -> Result<(), String> { - run(lexeme(), 512, deterministic, |s| { + run(lexeme(), 256, deterministic, |s| { let gate = s.parse::().is_ok_and(f64::is_finite); let parsed = parse(&s, Typing::Inferred); prop_assert_eq!( @@ -1205,7 +1205,7 @@ fn an_intermediate_beyond_i128_comes_back_exact() { #[test] fn the_canonical_text_reads_back_as_the_same_number() { - run(any_num(), 512, false, |n| { + run(any_num(), 256, false, |n| { let text = n.to_string(); let back = Num::from_canonical(&text); prop_assert!(