Ancient
view release on metacpan or search on metacpan
t/lib/TestVec.pm view on Meta::CPAN
my $eps = 1.0;
$eps /= 2 while (1.0 + $eps/2) != 1.0;
$NV_INFO{machine_epsilon} = $eps;
}
sub nv_info { return \%NV_INFO }
sub nv_epsilon { return $NV_INFO{machine_epsilon} }
sub nv_digits { return $NV_INFO{digits} }
sub is_quadmath { return $NV_INFO{is_quadmath} }
sub is_long_double { return $NV_INFO{is_long_double} }
# ============================================
# Tolerance Calculation
# ============================================
# Get appropriate absolute tolerance based on NV precision
sub get_tolerance {
my ($strict) = @_;
if ($NV_INFO{is_quadmath}) {
return $strict ? 1e-30 : 1e-12;
} elsif ($NV_INFO{is_long_double}) {
return $strict ? 1e-18 : 1e-12;
}
return $strict ? 1e-14 : 1e-9;
}
# Get relative tolerance (scales with magnitude)
sub relative_tolerance {
my ($ulps) = @_;
$ulps //= 4; # Default: 4 ULPs
return $NV_INFO{machine_epsilon} * $ulps;
}
# ============================================
# Comparison Methods
# ============================================
# 1. Absolute tolerance (original method)
sub approx_eq {
my ($got, $expected, $tolerance) = @_;
$tolerance //= get_tolerance();
return 1 if !defined $got && !defined $expected;
return 0 if !defined $got || !defined $expected;
return abs($got - $expected) < $tolerance;
}
# 2. Relative epsilon comparison (handles different magnitudes)
sub relatively_equal {
my ($got, $expected, $ulps) = @_;
$ulps //= 4;
return 1 if !defined $got && !defined $expected;
return 0 if !defined $got || !defined $expected;
# Handle exact zero
return $got == 0 if $expected == 0;
return $expected == 0 if $got == 0;
# Handle infinities
return $got == $expected if $got != $got || $expected != $expected; # NaN
# Relative comparison
my $max_abs = abs($got) > abs($expected) ? abs($got) : abs($expected);
my $diff = abs($got - $expected);
return $diff <= $NV_INFO{machine_epsilon} * $max_abs * $ulps;
}
# 3. ULP (Units in Last Place) comparison - most rigorous
# Counts how many representable floats apart two values are
sub ulp_distance {
my ($a, $b) = @_;
return 0 if !defined $a && !defined $b;
return -1 if !defined $a || !defined $b; # Error indicator
# Handle special cases
return 0 if $a == $b; # Exact match (handles infinities)
return -1 if $a != $a || $b != $b; # NaN
# For ULP calculation, we use the difference scaled by epsilon
# This is a portable approximation that works across NV types
my $max_abs = abs($a) > abs($b) ? abs($a) : abs($b);
$max_abs = 1.0 if $max_abs == 0; # Prevent division by zero
my $ulps = abs($a - $b) / ($NV_INFO{machine_epsilon} * $max_abs);
return int($ulps + 0.5); # Round to nearest integer
}
sub ulp_equal {
my ($got, $expected, $max_ulps) = @_;
$max_ulps //= 4;
my $dist = ulp_distance($got, $expected);
return 0 if $dist < 0; # Error case (NaN, undef)
return $dist <= $max_ulps;
}
# 4. Exact bit comparison - no tolerance
sub bits_equal {
my ($a, $b) = @_;
return 0 if !defined $a || !defined $b;
return pack("F", $a) eq pack("F", $b);
}
sub bits_hex {
my ($val) = @_;
return 'undef' unless defined $val;
return unpack("H*", pack("F", $val));
}
# ============================================
# Test::More Integration
# ============================================
# Primary test function - uses relative comparison by default
sub float_is {
my ($got, $expected, $name, $opts) = @_;
$opts //= {};
my $method = $opts->{method} // 'relative';
my $ulps = $opts->{ulps} // 4;
my $tolerance = $opts->{tolerance};
my $ok;
my $details = '';
if ($method eq 'exact') {
$ok = bits_equal($got, $expected);
$details = sprintf("got bits: %s, expected bits: %s",
bits_hex($got), bits_hex($expected));
}
elsif ($method eq 'ulp') {
my $dist = ulp_distance($got, $expected);
$ok = $dist >= 0 && $dist <= $ulps;
$details = sprintf("ULP distance: %d (max allowed: %d)", $dist, $ulps);
}
elsif ($method eq 'absolute') {
$tolerance //= get_tolerance();
$ok = approx_eq($got, $expected, $tolerance);
$details = sprintf("diff: %g, tolerance: %g",
abs(($got // 0) - ($expected // 0)), $tolerance);
}
else { # relative (default)
$ok = relatively_equal($got, $expected, $ulps);
if (defined $got && defined $expected && $expected != 0) {
my $rel_err = abs($got - $expected) / abs($expected);
$details = sprintf("relative error: %g, max allowed: %g",
$rel_err, $NV_INFO{machine_epsilon} * $ulps);
}
}
local $Test::Builder::Level = $Test::Builder::Level + 1;
ok($ok, $name);
( run in 0.572 second using v1.01-cache-2.11-cpan-df04353d9ac )