File Coverage

blib/lib/Encode/Float.pm
Criterion Covered Total %
statement 65 72 90.2
branch 19 26 73.0
condition 1 3 33.3
subroutine 7 8 87.5
pod 4 4 100.0
total 96 113 84.9


line stmt bran cond sub pod time code
1             package Encode::Float;
2 1     1   20707 use strict;
  1         2  
  1         38  
3              
4             BEGIN
5             {
6 1     1   5 use Exporter ();
  1         2  
  1         22  
7 1     1   4 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  1         5  
  1         143  
8 1     1   2 $VERSION = '0.11';
9 1         15 @ISA = qw(Exporter);
10 1         2 @EXPORT = qw();
11 1         2 @EXPORT_OK = qw();
12 1         691 %EXPORT_TAGS = ();
13             }
14              
15             #01234567890123456789012345678901234567890123
16             #Encode/decode float as a string for sorting.
17              
18             =head1 NAME
19              
20             C - Encode/decode float as a string for sorting.
21              
22             =head1 SYNOPSIS
23              
24             use Encode::Float;
25             my $encoder = Encode::Float->new();
26             my @list;
27             for (my $i = 0 ; $i < 10 ; $i++)
28             {
29             my $float = (.5 - rand) * 10**int(10 - 20 * rand);
30             $float = 0 if $i == 0;
31             my $encoded = $encoder->encode($float);
32             my $decoded = $encoder->decode($encoded);
33             my $error = $encoder->getRelativeDifference($float, $decoded);
34             push @list, [ $encoded, $float, $decoded, $error ];
35             }
36             @list = sort { $a->[0] cmp $b->[0] } @list;
37             foreach (@list)
38             {
39             print join(',', @$_) . "\n";
40             }
41              
42             =head1 DESCRIPTION
43              
44             C encodes and decodes floating point numbers
45             as fixed length positive decimal integers that preserve their order (less
46             rounding errors), that is, sorting the encoded integers also sorts the
47             floating point numbers.
48              
49             =head1 CONSTRUCTOR
50              
51             =head2 C
52              
53             The method C creates an instance of the C
54             class with the following parameter:
55              
56             =over
57              
58             =item C
59              
60             digitsOfAccuracy => 16
61              
62             C is an optional parameter that sets the number of
63             decimal digits to preserve in the floating point number; the default is 16.
64              
65             =back
66              
67             =cut
68              
69             sub new
70             {
71 1     1 1 11 my ($Class, %Parameters) = @_;
72 1   33     7 my $Self = bless({}, ref($Class) || $Class);
73              
74             # set the number of digits used to represent a float.
75 1         6 $Self->{digitsOfAccuracy} = 16;
76 1 50       4 $Self->{digitsOfAccuracy} = int abs $Parameters{digitsOfAccuracy} if exists $Parameters{digitsOfAccuracy};
77 1 50       3 $Self->{digitsOfAccuracy} = 1 if $Self->{digitsOfAccuracy} < 1;
78              
79             # get the maximum integer value.
80 1         4 my $mantissaMaxStr = '9' x $Self->{digitsOfAccuracy};
81 1         3 my $mantissaMax = $mantissaMaxStr + 0;
82 1 50       4 if ($mantissaMax ne $mantissaMaxStr)
83             {
84 0         0 die "digitsOfAccuracy = $Self->{digitsOfAccuracy} is too large.\n";
85             }
86 1         3 $Self->{mantissaMax} = $mantissaMax;
87 1         3 $Self->{floatFormat} = '%+' . '.' . ($Self->{digitsOfAccuracy} - 1) . 'E';
88 1         3 $Self->{exponentSize} = 3;
89 1         3 $Self->{exponentFormat} = '%+0' . ($Self->{exponentSize} + 1) . 'd';
90 1         3 $Self->{exponentMax} = '9' x $Self->{exponentSize};
91 1         3 $Self->{mantissaFormat} = '%0' . $Self->{digitsOfAccuracy} . 'd';
92 1         3 return $Self;
93             }
94              
95             =head1 METHODS
96              
97             =head2 C
98              
99             The method C takes a floating point number as its only parameter
100             and returns its integer encoding.
101              
102             =cut
103              
104             sub encode
105             {
106 256230     256230 1 3738079 my ($Self, $Float) = @_;
107              
108             # convert the float to a string.
109 256230         1281217 my $string = sprintf($Self->{floatFormat}, $Float);
110              
111             # get the mantissa of the float.
112 256230         576507 my $mantissa = substr($string, 1, 1) . substr($string, 3, $Self->{digitsOfAccuracy} - 1);
113              
114             # get the exponent of the float, with its sign.
115 256230         873198 my $exponent = sprintf($Self->{exponentFormat}, substr($string, $Self->{digitsOfAccuracy} + 3));
116              
117             # encode the sign of the float and the exponent to a single leading digit.
118 256230         378238 my $lead;
119 256230 100       485319 if ($Float < 0)
120             {
121              
122             # the float is negative, so take the complement of the mantissa.
123 127676         367783 $mantissa = sprintf($Self->{mantissaFormat}, $Self->{mantissaMax} - $mantissa);
124 127676 100       251379 if (substr($exponent, 0, 1) eq '-')
125             {
126 63982         88935 $lead = 2;
127             }
128             else
129             {
130 63694         78170 $lead = 1;
131              
132             # negative float but positive exponent, so take the complement of the exponent.
133 63694         158390 $exponent = sprintf($Self->{exponentFormat}, $Self->{exponentMax} - $exponent);
134             }
135             }
136             else
137             {
138 128554 100       239065 if (substr($exponent, 0, 1) eq '-')
139             {
140 64206         77047 $lead = 3;
141              
142             # positive float but negative exponent, so take the complement of the exponent.
143 64206         182866 $exponent = sprintf($Self->{exponentFormat}, $Self->{exponentMax} + $exponent);
144             }
145             else
146             {
147 64348         90887 $lead = 4;
148             }
149             }
150              
151             # zero is a special case.
152 256230 100       527255 $lead = 3 if $Float == 0;
153              
154             # encode the float as a long integer that preserves sort order.
155 256230         912788 return $lead . substr($exponent, 1) . $mantissa;
156             }
157              
158             =head2 C
159              
160             The method C takes an encoded floating point number (a positive
161             integer) and returns its floating point number.
162              
163             =cut
164              
165             sub decode
166             {
167 128115     128115 1 179934 my ($Self, $EncodedFloat) = @_;
168              
169             # holds the sign of the float.
170 128115         131890 my $sign;
171              
172             # get the leading digit that encodes the sign of the float and exponent.
173 128115         183635 my $lead = substr($EncodedFloat, 0, 1);
174              
175             # get the exponent of the float.
176 128115         203585 my $exponent = substr($EncodedFloat, 1, $Self->{exponentSize});
177              
178             # get the mantissa of the float.
179 128115         225544 my $mantissa = substr($EncodedFloat, $Self->{exponentSize} + 1);
180              
181             # adjust the exponent and sign via the leading digit.
182 128115 100       345856 if ($lead == 1)
    100          
    100          
183             {
184 31847         37327 $sign = -1;
185 31847         48349 $mantissa = $Self->{mantissaMax} - $mantissa;
186 31847         57702 $exponent = $Self->{exponentMax} - $exponent;
187             }
188             elsif ($lead == 2)
189             {
190 31991         42043 $sign = -1;
191 31991         48921 $mantissa = $Self->{mantissaMax} - $mantissa;
192 31991         46975 $exponent = -$exponent;
193             }
194             elsif ($lead == 3)
195             {
196 32108         37504 $sign = 1;
197 32108         52580 $exponent -= $Self->{exponentMax};
198             }
199             else
200             {
201 32169         41992 $sign = 1;
202             }
203              
204             # return the float.
205 128115         186245 $mantissa =~ s/^0+//;
206 128115 100       243777 return 0 unless length $mantissa;
207 128110         232644 my $decimal = substr($mantissa, 0, 1) . '.' . substr($mantissa, 1);
208 128110         1391076 return $sign * $decimal * 10**$exponent;
209             }
210              
211             =head2 C
212              
213             The method C computes the relative
214             difference between the floating point numbers C and C, which
215             is C or zero if both
216             numbers are zero.
217              
218             =cut
219              
220             sub getRelativeDifference
221             {
222 0     0 1   my ($Self, $FloatA, $FloatB) = @_;
223 0           my $absMax = abs $FloatA;
224 0           my $absFloatB = abs $FloatB;
225 0 0         $absMax = $absFloatB if $absFloatB > $absMax;
226 0 0         return 0 unless $absMax;
227 0           return abs($FloatA - $FloatB) / $absMax;
228             }
229              
230             =head1 INSTALLATION
231              
232             Use L to install the module and all its prerequisites:
233              
234             perl -MCPAN -e shell
235             >install Encode::Float
236              
237             =head1 BUGS
238              
239             Please email bugs reports or feature requests to C, or through
240             the web interface at L. The author
241             will be notified and you can be automatically notified of progress on the bug fix or feature request.
242              
243             =head1 AUTHOR
244              
245             Jeff Kubina
246              
247             =head1 COPYRIGHT
248              
249             Copyright (c) 2013 Jeff Kubina. All rights reserved.
250             This program is free software; you can redistribute
251             it and/or modify it under the same terms as Perl itself.
252              
253             The full text of the license can be found in the
254             LICENSE file included with this module.
255              
256             =head1 KEYWORDS
257              
258             decoding, double, encoding, float
259              
260             =head1 SEE ALSO
261              
262             L, L
263              
264             =cut
265              
266             1;
267              
268             # The preceding line will help the module return a true value