File Coverage

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


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