File Coverage

blib/lib/Lingua/EN/Fractions.pm
Criterion Covered Total %
statement 39 39 100.0
branch 16 16 100.0
condition 3 3 100.0
subroutine 7 7 100.0
pod 0 1 0.0
total 65 66 98.4


line stmt bran cond sub pod time code
1             package Lingua::EN::Fractions;
2             $Lingua::EN::Fractions::VERSION = '0.06';
3 6     6   138248 use 5.008;
  6         21  
  6         236  
4 6     6   33 use strict;
  6         13  
  6         194  
5 6     6   27 use warnings;
  6         27  
  6         204  
6 6     6   5121 use utf8;
  6         52  
  6         35  
7              
8 6     6   5113 use parent 'Exporter';
  6         1883  
  6         31  
9 6     6   5764 use Lingua::EN::Numbers qw/ num2en num2en_ordinal /;
  6         13768  
  6         3378  
10              
11             our @EXPORT_OK = qw/ fraction2words /;
12              
13             my %special_denominators =
14             (
15             2 => { singular => 'half', plural => 'halve' },
16             4 => { singular => 'quarter', plural => 'quarter' },
17             );
18              
19             my %unicode =
20             (
21             '¼' => '1/4',
22             '½' => '1/2',
23             '¾' => '3/4',
24             '⅓' => '1/3',
25             '⅔' => '2/3',
26             '⅕' => '1/5',
27             '⅖' => '2/5',
28             '⅗' => '3/5',
29             '⅘' => '4/5',
30             '⅙' => '1/6',
31             '⅚' => '5/6',
32             '⅛' => '1/8',
33             '⅜' => '3/8',
34             '⅝' => '5/8',
35             '⅞' => '7/8',
36             '⁄' => '/', # FRACTION SLASH (U+2044)
37             '−' => '-', # MINUS SIGN (U+2212)
38             );
39             my $unicode_regexp = join('|', keys %unicode);
40              
41             sub fraction2words
42             {
43 30     30 0 15663 my $number = shift;
44 30         114 my $fraction = qr|
45             ^
46             (\s*-)?
47             (\s*([0-9]+)\s+)?
48             \s*
49             ([0-9]+)
50             \s*
51             /
52             \s*
53             ([0-9]+)
54             \s*
55             $
56             |x;
57              
58 30         470 $number =~ s/($unicode_regexp)/ $unicode{$1}/g;
59              
60 30 100       319 if (my ($negate, $preamble, $wholepart, $numerator, $denominator) = $number =~ $fraction) {
61 29         39 my $denominator_as_words = do {
62 29 100       61 if (exists $special_denominators{$denominator}) {
63 12 100       28 if ($numerator == 1) {
64 8         27 $special_denominators{ $denominator }->{singular};
65             }
66             else {
67 4         15 $special_denominators{ $denominator }->{plural};
68             }
69             }
70             else {
71 17         50 num2en_ordinal($denominator);
72             }
73             };
74 29         433 my $numerator_as_words = do {
75 29 100 100     109 if ($numerator == 1 && $wholepart) {
76             # "1 1/2" -> "one and *a* half"
77             # "1 1/8" -> "one and *an* eighth"
78 7 100       25 $denominator_as_words =~ /^[aeiou]/i ? 'an' : 'a';
79             }
80             else {
81 22         55 num2en($numerator);
82             }
83             };
84 29         228 my $phrase = '';
85            
86 29 100       97 $phrase .= 'minus ' if $negate;
87 29 100       74 $phrase .= num2en($wholepart).' and ' if $wholepart;
88 29         153 $phrase .= "$numerator_as_words $denominator_as_words";
89 29 100       62 $phrase .= 's' if $numerator > 1;
90 29         117 return $phrase;
91             }
92              
93 1         4 return undef;
94             }
95              
96             1;
97              
98             =encoding utf8
99              
100             =head1 NAME
101              
102             Lingua::EN::Fractions - convert "3/4" into "three quarters", etc
103              
104             =head1 SYNOPSIS
105              
106             use Lingua::EN::Fractions qw/ fraction2words /;
107              
108             my $fraction = '3/4';
109             my $as_words = fraction2words($fraction);
110              
111             Or using L:
112              
113             use Number::Fraction;
114              
115             my $fraction = Number::Fraction->new(2, 7);
116             my $as_words = fraction2words($fraction);
117              
118             =head1 DESCRIPTION
119              
120             This module provides a function, C,
121             which takes a string containing a fraction and returns
122             the English phrase for that fraction.
123             If no fraction was found in the input, then C is returned.
124              
125             For example
126              
127             fraction2words('1/2'); # "one half"
128             fraction2words('3/4'); # "three quarters"
129             fraction2words('5/17'); # "five seventeenths"
130             fraction2words('5'); # undef
131             fraction2words('-3/5'); # "minus three fifths"
132              
133             You can also pass a whole number ahead of the fraction:
134              
135             fraction2words('1 1/2'); # "one and a half"
136             fraction2words('-1 1/8'); # "minus one and an eighth"
137             fraction2words('12 3/4'); # "twelve and three quarters"
138              
139             Note that instead of "one and one half",
140             you'll get back "one and a half".
141              
142             =head2 Unicode fraction characters
143              
144             As of version 0.05,
145             certain Unicode characters are also supported. For example:
146              
147             fraction2words('½') # "one half"
148             fraction2words('1⅜') # "one and three eighths"
149             fraction2words('-1⅘') # "minus one and four fifths"
150              
151             You can also use the Unicode FRACTION SLASH, which is a different
152             character from the regular slash:
153              
154             fraction2words('1/2') # "one half"
155             fraction2words('1⁄2') # "one half"
156              
157             As of version 0.06, you an also use the Unicode MINUS SIGN:
158              
159             fraction2words('−1/2') # "minus one half"
160             fraction2words('−⅘') # "minus four fifths"
161              
162             At the moment, the DIVISION SLASH character isn't handled.
163             Feel free to tell me if you think I got that wrong.
164              
165             =head2 Working with Number::Fraction
166              
167             You can also pass in a fraction represented using L:
168              
169             $fraction = Number::Fraction->new(2, 7);
170             $as_words = fraction2words($fraction); # "two sevenths"
171              
172             =head1 CAVEATS
173              
174             At the moment, no attempt is made to simplify the fraction,
175             so C<'5/2'> will return "five halves" rather than "two and a half".
176             Note though, that if you're using L, then it
177             does normalise fractions, so "3/6" will become "1/2".
178              
179             At the moment it's not very robust to weird inputs.
180              
181             =head1 SEE ALSO
182              
183             L,
184             L,
185             L - other modules for converting numbers
186             into words.
187              
188             L - a class for representing fractions and
189             operations on them.
190              
191             =head1 REPOSITORY
192              
193             L
194              
195             =head1 AUTHOR
196              
197             Neil Bowers Eneilb@cpan.orgE
198              
199             This module was suggested by Sean Burke, who created the
200             other C modules that I now maintain.
201              
202             =head1 COPYRIGHT AND LICENSE
203              
204             This software is copyright (c) 2014 by Neil Bowers .
205              
206             This is free software; you can redistribute it and/or modify it under
207             the same terms as the Perl 5 programming language system itself.
208              
209             =cut