File Coverage

blib/lib/Number/Convert/Roman.pm
Criterion Covered Total %
statement 9 64 14.0
branch 0 26 0.0
condition 0 9 0.0
subroutine 3 6 50.0
pod 3 3 100.0
total 15 108 13.8


line stmt bran cond sub pod time code
1             package Number::Convert::Roman;
2              
3             # Number::Convert::Roman - Roman-Arabic numeral converter
4              
5             # Copyright (c) 2015 José Santos. All rights reserved.
6             # This program is free software. It can be redistributed and/or modified under
7             # the same terms as Perl itself.
8              
9 1     1   19192 use 5.006;
  1         4  
  1         33  
10 1     1   5 use strict;
  1         2  
  1         32  
11 1     1   4 use warnings;
  1         5  
  1         691  
12              
13             our $VERSION = '0.01';
14              
15             my %ROMAN_ARABIC = ('I' => 1, 'V' => 5, 'X' => 10, 'L' => 50,
16             'C' => 100, 'D' => 500, 'M' => 1000);
17             my %ARABIC_ROMAN = map { $ROMAN_ARABIC{$_} => $_ } keys %ROMAN_ARABIC;
18             my ($THOUSANDS_OPEN, $THOUSANDS_CLOSE) = ('(', ')');
19              
20 0     0 1   sub new { bless {}, shift; }
21              
22             sub arabic {
23 0     0 1   shift;
24 0           my @roman = split //, shift;
25 0           my ($number, $sum, $last, $thousands) = (0, 0, 0, 0);
26 0           for (my $i = $#roman; $i >= 0; $i--) {
27 0 0         if ($roman[$i] !~ /[$THOUSANDS_CLOSE$THOUSANDS_OPEN]/) {
    0          
28 0           $number = $ROMAN_ARABIC{$roman[$i]};
29 0 0         $sum += ($number >= $last ? 1 : -1) * ($number * 1000 ** $thousands);
30 0           $last = $number;
31             } elsif ($roman[$i] eq $THOUSANDS_CLOSE) {
32 0           $last = 0;
33 0           $thousands++;
34             }
35             }
36 0           $sum;
37             }
38              
39             sub roman {
40 0     0 1   shift;
41 0           my ($i, @arabic) = (-1, split //, shift);
42 0 0         my @numbers = map { $i++; $_ > 0 ? $_ . '0' x ($#arabic - $i) : () } @arabic;
  0            
  0            
43 0           my ($prefix, $x, $y, $first, $thousands, $zerofill);
44             # break each number using roman logic and underscore prefix each power of 1000
45             # e.g. 1000 -> _1; 3000 -> _1, _1, _1; 4000 -> _1, _5; 5000000 -> __5
46 0           for ($i = 0; $i <= $#numbers; $i++) {
47 0           $prefix = '';
48 0           $first = substr $numbers[$i], 0, 1;
49 0           $thousands = int(log($numbers[$i] / $first) / log(1000));
50 0           $prefix = '_' x $thousands;
51 0           $numbers[$i] /= 1000 ** $thousands;
52 0           $zerofill = '0' x ((length $numbers[$i]) - 1);
53 0 0 0       if ($first == 1 || $first == 5) {
    0 0        
    0 0        
    0          
    0          
54 0           $numbers[$i] = $prefix . $numbers[$i];
55             } elsif ($first >= 2 && $first <= 3) { # replace [2-3]0* by 2-3 x _+10*
56 0           splice @numbers, $i, 1;
57 0           for (1 .. $first) {
58 0           splice @numbers, $i, 0, $prefix . 1 . $zerofill;
59             }
60 0           $i += $first - 1;
61             } elsif ($first == 4) { # replace 40* by 10* and 50*
62 0           splice @numbers, $i, 1;
63 0           splice @numbers, $i++, 0, $prefix . 1 . $zerofill, $prefix . 5 . $zerofill;
64             } elsif ($first >= 6 and $first <= 8) { # replace [6-8]0* by 50* + 1-3 x +10*
65 0           splice @numbers, $i, 1;
66 0           for (6 .. $first) {
67 0           splice @numbers, $i, 0, $prefix . 1 . $zerofill;
68             }
69 0           splice @numbers, $i, 0, $prefix . 5 . $zerofill;
70 0           $i += ($first - 5);
71             } elsif ($first == 9) { # replace 90* by 10* and 50*
72 0           splice @numbers, $i, 1;
73 0           splice @numbers, $i++, 0, $prefix . 1 . $zerofill, $prefix . 10 . $zerofill;
74             }
75             }
76             # replace each underscore prefixed number by its parenthesis surrounded version
77             # e.g. _1 -> (I); _1, _1, _1 -> (III); _1, _5 -> (IV); __5 -> ((V))
78 0           my ($result, $previous_level, $level, $number) = ('', -1, 0, '');
79 0           for (@numbers) {
80 0           ($prefix, $number) = m/(_*)(.+)/;
81 0           $level = length $prefix;
82 0 0         if ($level != $previous_level) {
83 0 0         if ($level < $previous_level) {
    0          
84 0           $result .= $THOUSANDS_CLOSE x ($previous_level - $level);
85             } elsif ($level > $previous_level) {
86 0           $result .= '(' x $level;
87             }
88 0           $previous_level = $level;
89             }
90 0           $result .= $ARABIC_ROMAN{$number};
91             }
92 0 0         if ($level eq $previous_level) {
93 0           $result .= $THOUSANDS_CLOSE x $level;
94             }
95             # replace all (I), (II) and (III) by M, MM and MMM, respectively
96 0           $result =~ s/\((I+)\)/'M' x length $1/ge;
  0            
97 0           $result;
98             }
99              
100             1;
101              
102             __END__