File Coverage

blib/lib/Test/Number/Delta.pm
Criterion Covered Total %
statement 80 80 100.0
branch 38 38 100.0
condition 6 9 66.6
subroutine 13 13 100.0
pod 4 4 100.0
total 141 144 97.9


line stmt bran cond sub pod time code
1 11     11   227600 use strict;
  11         28  
  11         521  
2 11     11   66 use warnings;
  11         24  
  11         1058  
3              
4             package Test::Number::Delta;
5             # ABSTRACT: Compare the difference between numbers against a given tolerance
6             our $VERSION = '1.04'; # VERSION
7              
8 11     11   74 use vars qw (@EXPORT @ISA);
  11         21  
  11         930  
9              
10             # Required modules
11 11     11   62 use Carp;
  11         19  
  11         898  
12 11     11   20171 use Test::Builder;
  11         52364  
  11         442  
13 11     11   66 use Exporter;
  11         24  
  11         31577  
14              
15             @ISA = qw( Exporter );
16             @EXPORT = qw( delta_not_ok delta_ok delta_within delta_not_within );
17              
18              
19             my $Test = Test::Builder->new;
20             my $Epsilon = 1e-6;
21             my $Relative = undef;
22              
23             sub import {
24 13     13   9021 my $self = shift;
25 13         50 my $pack = caller;
26 13         305 my $found = grep /within|relative/, @_;
27 13 100       515 croak "Can't specify more than one of 'within' or 'relative'"
28             if $found > 1;
29 12 100       46 if ($found) {
30 8         34 my ( $param, $value ) = splice @_, 0, 2;
31 8 100       12528 croak "'$param' parameter must be non-zero"
32             if $value == 0;
33 6 100       33 if ( $param eq 'within' ) {
    100          
34 3         8 $Epsilon = abs($value);
35             }
36             elsif ( $param eq 'relative' ) {
37 2         8 $Relative = abs($value);
38             }
39             else {
40 1         313 croak "Test::Number::Delta parameters must come first";
41             }
42             }
43 9         57 $Test->exported_to($pack);
44 9         100 $Test->plan(@_);
45 9         38811 $self->export_to_level( 1, $self, $_ ) for @EXPORT;
46             }
47              
48             #--------------------------------------------------------------------------#
49             # _check -- recursive function to perform comparison
50             #--------------------------------------------------------------------------#
51              
52             sub _check {
53 106     106   185 my ( $p, $q, $epsilon, $name, @indices ) = @_;
54 106         260 my ( $ok, $diag ) = ( 1, q{} ); # assume true
55 106 100 66     452 if ( ref $p eq 'ARRAY' || ref $q eq 'ARRAY' ) {
56 28 100       51 if ( @$p == @$q ) {
57 27         30 for my $i ( 0 .. $#{$p} ) {
  27         653  
58 50         52 my @new_indices;
59 50 100       161 ( $ok, $diag, @new_indices ) = _check( $p->[$i], $q->[$i], $epsilon, $name,
60             scalar @indices ? @indices : (), $i, );
61 50 100       145 if ( not $ok ) {
62 10         15 @indices = @new_indices;
63 10         19 last;
64             }
65             }
66             }
67             else {
68 1         2 $ok = 0;
69 1         4 $diag =
70             "Got an array of length "
71             . scalar(@$p)
72             . ", but expected an array of length "
73             . scalar(@$q);
74             }
75             }
76             else {
77 78         157 $ok = abs( $p - $q ) < $epsilon;
78 78 100       152 if ( !$ok ) {
79 26         55 my ( $ep, $dp ) = _ep_dp($epsilon);
80 26         295 $diag = sprintf( "%.${dp}f and %.${dp}f are not equal" . " to within %.${ep}f",
81             $p, $q, $epsilon );
82             }
83             }
84 106 100       566 return ( $ok, $diag, scalar(@indices) ? @indices : () );
85             }
86              
87             sub _ep_dp {
88 50     50   69 my $epsilon = shift;
89 50         527 my ($exp) = sprintf( "%e", $epsilon ) =~ m/e(.+)/;
90 50 100       151 my $ep = $exp < 0 ? -$exp : 1;
91 50         76 my $dp = $ep + 1;
92 50         102 return ( $ep, $dp );
93             }
94              
95              
96             #--------------------------------------------------------------------------#
97             # delta_within()
98             #--------------------------------------------------------------------------#
99              
100              
101             sub delta_within($$$;$) { ## no critic
102 33     33 1 9787 my ( $p, $q, $epsilon, $name ) = @_;
103 33 100       378 croak "Value of epsilon to delta_within must be non-zero"
104             if $epsilon == 0;
105 32         49 $epsilon = abs($epsilon);
106 32         82 my ( $ok, $diag, @indices ) = _check( $p, $q, $epsilon, $name );
107 32 100       143 if (@indices) {
108 3         12 $diag = "At [" . join( "][", @indices ) . "]: $diag";
109             }
110 32   66     135 return $Test->ok( $ok, $name ) || $Test->diag($diag);
111             }
112              
113             #--------------------------------------------------------------------------#
114             # delta_ok()
115             #--------------------------------------------------------------------------#
116              
117              
118             sub delta_ok($$;$) { ## no critic
119 26     26 1 24311 my ( $p, $q, $name ) = @_;
120             {
121 26         44 local $Test::Builder::Level = $Test::Builder::Level + 1;
  26         51  
122 26 100       110 my $e =
    100          
123             $Relative
124             ? $Relative * ( abs($p) > abs($q) ? abs($p) : abs($q) )
125             : $Epsilon;
126 26         73 delta_within( $p, $q, $e, $name );
127             }
128             }
129              
130             #--------------------------------------------------------------------------#
131             # delta_not_ok()
132             #--------------------------------------------------------------------------#
133              
134              
135             sub delta_not_within($$$;$) { ## no critic
136 25     25 1 7680 my ( $p, $q, $epsilon, $name ) = @_;
137 25 100       1255 croak "Value of epsilon to delta_not_within must be non-zero"
138             if $epsilon == 0;
139 24         29 $epsilon = abs($epsilon);
140 24         49 my ( $ok, undef, @indices ) = _check( $p, $q, $epsilon, $name );
141 24         38 $ok = !$ok;
142 24         594 my ( $ep, $dp ) = _ep_dp($epsilon);
143 24         160 my $diag = sprintf( "Arguments are equal to within %.${ep}f", $epsilon );
144 24   66     83 return $Test->ok( $ok, $name ) || $Test->diag($diag);
145             }
146              
147              
148             sub delta_not_ok($$;$) { ## no critic
149 18     18 1 18734 my ( $p, $q, $name ) = @_;
150             {
151 18         51 local $Test::Builder::Level = $Test::Builder::Level + 1;
  18         30  
152 18 100       62 my $e =
    100          
153             $Relative
154             ? $Relative * ( abs($p) > abs($q) ? abs($p) : abs($q) )
155             : $Epsilon;
156 18         42 delta_not_within( $p, $q, $e, $name );
157             }
158             }
159              
160             1;
161              
162             __END__