pub fn erfc(x: f64) -> f64 {
libm::erfc(x)
}
pub const INV_SQRT_2PI: f64 = 0.3989422804014327;
pub const SQRT_2: f64 = 1.4142135623730951;
pub const LN_2: f64 = 0.6931471805599453;
pub const INV_SQRT_PI: f64 = 0.5641895835477563;
pub const SQRT_2_OVER_PI: f64 = 0.7978845608028654;
#[cfg(test)]
mod probit_parity_tests {
use super::*;
use crate::numerics_device::PROBIT_NUMERICS_CU;
const EPS: f64 = f64::EPSILON;
fn ulp(got: f64, want: f64) -> f64 {
if want == 0.0 {
(got - want).abs() / EPS
} else {
(got - want).abs() / (EPS * want.abs())
}
}
fn literal_after(src: &str, needle: &str) -> f64 {
let start = src
.find(needle)
.unwrap_or_else(|| panic!("kernel source is missing marker {needle:?}"))
+ needle.len();
let tail = &src[start..];
let num_start = tail
.find(|c: char| c == '-' || c == '.' || c.is_ascii_digit())
.unwrap_or_else(|| panic!("no numeric literal follows {needle:?}"));
let rest = &tail[num_start..];
let end = rest
.find(|c: char| !(c.is_ascii_digit() || matches!(c, '.' | 'e' | 'E' | '+' | '-')))
.unwrap_or(rest.len());
rest[..end]
.parse::<f64>()
.unwrap_or_else(|e| panic!("failed to parse literal after {needle:?}: {e}"))
}
#[test]
fn host_constants_match_kernel_source_bit_for_bit() {
for (needle, host) in [
("#define INV_SQRT_2PI", INV_SQRT_2PI),
("#define SQRT_2", SQRT_2),
("#define LN_2", LN_2),
("inv_sqrt_pi =", INV_SQRT_PI),
("sqrt_2_over_pi =", SQRT_2_OVER_PI),
] {
let device = literal_after(PROBIT_NUMERICS_CU, needle);
assert_eq!(
device.to_bits(),
host.to_bits(),
"constant {needle:?} drifted: kernel={device:?} host={host:?}"
);
}
}
#[test]
fn kernel_source_uses_msun_transcendentals_only() {
for good in ["erfc(", "exp(", "log(", "log1p("] {
assert!(
PROBIT_NUMERICS_CU.contains(good),
"kernel source should call msun `{good}`"
);
}
for bad in [
"__expf",
"__logf",
"expf(",
"logf(",
"erfcf(",
"__fdividef",
"__frcp",
"use_fast_math",
"ffast-math",
"__dmul_",
"__dadd_",
"__fmaf",
] {
assert!(
!PROBIT_NUMERICS_CU.contains(bad),
"kernel source must not use fast-math / single-precision `{bad}`"
);
}
}
#[test]
fn erfc_boundary_and_symmetry() {
assert_eq!(erfc(0.0), 1.0);
let mut worst = 0.0_f64;
for i in 0..300 {
let x = i as f64 * 0.01;
worst = worst.max(ulp(erfc(-x), 2.0 - erfc(x)));
}
assert!(worst <= 2.0, "erfc symmetry drift {worst:.3} ULP > 2");
}
}