test: localise the erfc_inv tail residual to the caller's argument
Scouting crates.io for a more accurate `erfc` than `libm` turned up a 1.16e-12 relative error in `erfc_inv` at `p_draw = 0.999999`, measured against a 70-digit `decimal` reference. It looked like too few Newton steps. It is not: adding a fourth changed nothing. The error is in forming the argument. `1.0 - 0.999999` is `1.0000000000287557e-06` — 0.999999 is not representable, and subtracting from one cancels, leaving 2.9e-11 of relative error before `erfc_inv` is entered. Given an exactly-representable argument it returns 1.8e-16. So the routine was never the problem, and the extra iteration has been reverted rather than shipped as a fix for a defect that was not there. `puruspe::inverfc` returns the identical wrong value for the identical reason, which is what makes the shared upstream cause obvious. Adds a test that separates the two, and corrects a quantile constant in `erfc_inv_matches_known_quantiles` that was recalled rather than computed: `Phi^-1(0.9999995)` is 4.89163847569859, not 4.891638475699099. The others were checked against the same reference and were right. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hcFjNDmHXZF8URGLku5zZ
This commit is contained in:
+24
@@ -863,6 +863,30 @@ mod tests {
|
|||||||
/// The draw margin must grow with the draw probability. It did not: it ran
|
/// The draw margin must grow with the draw probability. It did not: it ran
|
||||||
/// 0.674 -> 1.476 -> 0.503 -> 0.982 as `p_draw` went 0.5 -> 0.9 -> 0.99 ->
|
/// 0.674 -> 1.476 -> 0.503 -> 0.982 as `p_draw` went 0.5 -> 0.9 -> 0.99 ->
|
||||||
/// 0.999, which is not a rounding error but a broken function.
|
/// 0.999, which is not a rounding error but a broken function.
|
||||||
|
/// Deep in the tail the accuracy limit is the *caller's* argument, not this
|
||||||
|
/// function.
|
||||||
|
///
|
||||||
|
/// `compute_margin(0.999999, ..)` computes `1.0 - p_draw`, and 0.999999 is
|
||||||
|
/// not representable: the subtraction cancels and leaves 2.9e-11 of
|
||||||
|
/// relative error in the argument before `erfc_inv` is even entered. Given
|
||||||
|
/// an exactly-representable argument the result is good to 1.8e-16, so this
|
||||||
|
/// is inherent to taking `p_draw` near one rather than something to fix
|
||||||
|
/// here. At `p_draw = 0.999` the whole path is still accurate to 4e-16.
|
||||||
|
///
|
||||||
|
/// Worth pinning: measured against a 70-digit reference, `puruspe`'s
|
||||||
|
/// `inverfc` returns the identical wrong value for the identical reason,
|
||||||
|
/// which is what makes it clear the fault is upstream of both.
|
||||||
|
#[test]
|
||||||
|
fn erfc_inv_is_exact_given_an_exactly_representable_argument() {
|
||||||
|
// erfc(z / sqrt2) = 1e-6 exactly, so z = Phi^-1(0.9999995).
|
||||||
|
let got = SQRT_2 * erfc_inv(1e-6);
|
||||||
|
let exact = 4.891_638_475_698_59;
|
||||||
|
assert!(
|
||||||
|
(got - exact).abs() / exact < 1e-14,
|
||||||
|
"got {got}, exact {exact}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compute_margin_is_monotone_in_the_draw_probability() {
|
fn compute_margin_is_monotone_in_the_draw_probability() {
|
||||||
let mut previous = 0.0;
|
let mut previous = 0.0;
|
||||||
|
|||||||
Reference in New Issue
Block a user