File Coverage

blib/lib/Math/Round.pm
Criterion Covered Total %
statement 78 80 97.5
branch 38 44 86.3
condition n/a
subroutine 18 18 100.0
pod 10 10 100.0
total 144 152 94.7


line stmt bran cond sub pod time code
1             package Math::Round;
2             $Math::Round::VERSION = '0.08';
3 1     1   68955 use 5.006;
  1         4  
4 1     1   6 use strict;
  1         2  
  1         18  
5 1     1   5 use warnings;
  1         2  
  1         22  
6              
7 1     1   548 use POSIX ();
  1         6428  
  1         32  
8 1     1   6 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         2  
  1         99  
9              
10 1     1   436 use parent 'Exporter';
  1         261  
  1         6  
11              
12             our @EXPORT = qw/ round nearest /;
13             our @EXPORT_OK = qw/ round nearest round_even round_odd round_rand
14             nearest_ceil nearest_floor nearest_rand
15             nlowmult nhimult /;
16              
17             our %EXPORT_TAGS = ( all => [ @EXPORT_OK ] );
18              
19             #--- Default value for "one-half". This is the lowest value that
20             #--- gives acceptable results for test #6 in test.pl. See the pod
21             #--- for more information.
22              
23             our $half = 0.50000000000008;
24              
25             sub round {
26 4     4 1 87 my $x;
27             my @res = map {
28 4 100       7 if ($_ >= 0) { POSIX::floor($_ + $Math::Round::half); }
  5         32  
  3         23  
29 2         7 else { POSIX::ceil($_ - $Math::Round::half); }
30             } @_;
31              
32 4 100       19 return (wantarray) ? @res : $res[0];
33             }
34              
35             sub round_even {
36             my @res = map {
37 3     3 1 525 my ($sign, $in, $fr) = _sepnum($_);
  4         8  
38 4 100       9 if ($fr == 0.5) {
39 2 100       10 $sign * (($in % 2 == 0) ? $in : $in + 1);
40             } else {
41 2         10 $sign * POSIX::floor(abs($_) + $Math::Round::half);
42             }
43             } @_;
44 3 100       12 return (wantarray) ? @res : $res[0];
45             }
46              
47             sub round_odd {
48             my @res = map {
49 4     4 1 295 my ($sign, $in, $fr) = _sepnum($_);
  5         11  
50 5 100       12 if ($fr == 0.5) {
51 2 100       27 $sign * (($in % 2 == 1) ? $in : $in + 1);
52             } else {
53 3         12 $sign * POSIX::floor(abs($_) + $Math::Round::half);
54             }
55             } @_;
56 4 100       15 return (wantarray) ? @res : $res[0];
57             }
58              
59             sub round_rand {
60             my @res = map {
61 3     3 1 320 my ($sign, $in, $fr) = _sepnum($_);
  4         8  
62 4 50       7 if ($fr == 0.5) {
63 0 0       0 $sign * ((rand(4096) < 2048) ? $in : $in + 1);
64             } else {
65 4         16 $sign * POSIX::floor(abs($_) + $Math::Round::half);
66             }
67             } @_;
68 3 100       11 return (wantarray) ? @res : $res[0];
69             }
70              
71             #--- Separate a number into sign, integer, and fractional parts.
72             #--- Return as a list.
73             sub _sepnum {
74 13     13   19 my $x = shift;
75 13 100       26 my $sign = ($x >= 0) ? 1 : -1;
76 13         30 $x = abs($x);
77 13         18 my $i = int($x);
78 13         32 return ($sign, $i, $x - $i);
79             }
80              
81             #------ "Nearest" routines (round to a multiple of any number)
82              
83             sub nearest {
84 5     5 1 270 my $targ = abs(shift);
85             my @res = map {
86 5 100       10 if ($_ >= 0) { $targ * int(($_ + $Math::Round::half * $targ) / $targ); }
  6         12  
  4         16  
87 2         9 else { $targ * POSIX::ceil(($_ - $Math::Round::half * $targ) / $targ); }
88             } @_;
89              
90 5 100       33 return (wantarray) ? @res : $res[0];
91             }
92              
93             # In the next two functions, the code for positive and negative numbers
94             # turns out to be the same. For negative numbers, the technique is not
95             # exactly obvious; instead of floor(x+0.5), we are in effect taking
96             # ceiling(x-0.5).
97              
98             sub nearest_ceil {
99 4     4 1 295 my $targ = abs(shift);
100 4         7 my @res = map { $targ * POSIX::floor(($_ + $Math::Round::half * $targ) / $targ) } @_;
  5         20  
101              
102 4 100       14 return wantarray ? @res : $res[0];
103             }
104              
105             sub nearest_floor {
106 4     4 1 298 my $targ = abs(shift);
107 4         8 my @res = map { $targ * POSIX::ceil(($_ - $Math::Round::half * $targ) / $targ) } @_;
  5         18  
108              
109 4 100       13 return wantarray ? @res : $res[0];
110             }
111              
112             sub nearest_rand {
113 3     3 1 273 my $targ = abs(shift);
114              
115             my @res = map {
116 3         5 my ($sign, $in, $fr) = _sepnear($_, $targ);
  4         8  
117 4 50       19 if ($fr == 0.5 * $targ) {
118 0 0       0 $sign * $targ * ((rand(4096) < 2048) ? $in : $in + 1);
119             } else {
120 4         14 $sign * $targ * int((abs($_) + $Math::Round::half * $targ) / $targ);
121             }
122             } @_;
123 3 100       8 return (wantarray) ? @res : $res[0];
124             }
125              
126             #--- Next lower multiple
127             sub nlowmult {
128 3     3 1 300 my $targ = abs(shift);
129 3         6 my @res = map { $targ * POSIX::floor($_ / $targ) } @_;
  4         14  
130              
131 3 100       12 return wantarray ? @res : $res[0];
132             }
133              
134             #--- Next higher multiple
135             sub nhimult {
136 3     3 1 262 my $targ = abs(shift);
137 3         6 my @res = map { $targ * POSIX::ceil($_ / $targ) } @_;
  4         13  
138              
139 3 100       12 return wantarray ? @res : $res[0];
140             }
141              
142             #--- Separate a number into sign, "integer", and "fractional" parts
143             #--- for the 'nearest' calculation. Return as a list.
144             sub _sepnear {
145 4     4   8 my ($x, $targ) = @_;
146 4 100       10 my $sign = ($x >= 0) ? 1 : -1;
147 4         5 $x = abs($x);
148 4         6 my $i = int($x / $targ);
149 4         9 return ($sign, $i, $x - $i*$targ);
150             }
151              
152             1;
153              
154             __END__