1313
1414class Converter implements IntegerConversion {
1515
16+ /*
17+ * Validate and convert
18+ */
1619 public function convert ( $ number ) {
20+ // standalone validator with default settings
1721 $ factory = new ValidatorFactory ( new Translator ( 'en ' ) );
1822 $ validation = $ factory ->make (
1923 array (
@@ -31,6 +35,9 @@ public function convert( $number ) {
3135 return $ this ->toRomanNumerals ( $ number );
3236 }
3337
38+ /*
39+ * Convert number into roman numeral
40+ */
3441 public function toRomanNumerals ( $ number ) {
3542 $ result = '' ;
3643 $ number = (int ) $ number ;
@@ -40,6 +47,8 @@ public function toRomanNumerals( $number ) {
4047 return false ;
4148 }
4249
50+ // Dictionary contains only the basic numbers and the
51+ // rest is calculated basing on it
4352 $ dictionary = array (
4453 1 => 'I ' ,
4554 5 => 'V ' ,
@@ -50,30 +59,45 @@ public function toRomanNumerals( $number ) {
5059 1000 => 'M '
5160 );
5261
53- // end early if key exists
62+ // end early if key already exists
5463 if ( array_key_exists ( $ number , $ dictionary ) ) {
5564 return $ dictionary [ $ number ];
5665 }
5766
67+ // used for $part calculation (more description in loop)
5868 $ temp = 0 ;
5969
70+ /*
71+ * The idea here is to split numbers into chunks so for ex. '1345' becomes 1000, 300, 40, 5
72+ * and then each chunk is looked up in dictionary.
73+ */
6074 for ( $ i = 1000 ; (int ) $ i !== 0 ; $ i /= 10 ) {
75+ // current chunk is the difference of given number, $i
76+ // and sum of previous chunks from the loop
6177 $ part = $ number - ( (int ) $ number % $ i ) - $ temp ;
6278
79+ // don't calculate when it's not needed
80+ // for ex. 45 doesn't need '1000' chunk
81+ // and when there are no chunks left we can break
6382 if ( $ i > $ number || $ part == 0 ) {
6483 continue ;
6584 }
6685
6786 $ closest = $ this ->closest ( $ dictionary , $ part );
87+
88+ // either number is closer to prev character in dictionary or to next one
6889 if ( $ closest < $ part ) {
90+ // numbers that are lower can have repeated character ( like I, II, III)
6991 $ repeat = floor ( $ part / $ closest );
7092 $ result .= str_repeat ( $ dictionary [ $ closest ], $ repeat );
7193
94+ // should there be something appended (like to V : VI, VII)
7295 $ after = $ this ->toRomanNumerals ( $ part - ( $ closest * $ repeat ) );
7396 if ( $ after ) {
7497 $ result .= $ after ;
7598 }
7699 } else {
100+ // should there be something prepended (like to V : IV)
77101 $ before = $ this ->toRomanNumerals ( $ closest - $ part );
78102 if ( $ before ) {
79103 $ result .= $ before ;
@@ -82,20 +106,30 @@ public function toRomanNumerals( $number ) {
82106 $ result .= $ dictionary [ $ closest ];
83107 }
84108
109+ // sum of all previous chunks
85110 $ temp += $ part ;
86111 }
87112
88113 return $ result ;
89114 }
90115
91116 function closest ( $ array , $ number ) {
117+ /*
118+ * This ratio was taken from the overall idea how roman numbers work.
119+ * For ex. after I there are 2 numbers that append it and before V there is one that prepends is.
120+ * It works same way for all other roman letters/numbers. The point at which this 'switch' happens
121+ * is exactly after third of all numbers (I, II, III, IV, V) so it happens at fourth of 5 numbers.
122+ * It's position is 4/5 which translates into 0.8.
123+ */
92124 $ ratio = 0.8 ;
93125
126+ // get the last and first element of array
94127 end ( $ array );
95128 $ next = key ( $ array );
96129 reset ( $ array );
97130 $ prev = key ( $ array );
98131
132+ // this way we know which element is before and after the given number
99133 foreach ( $ array as $ key => $ val ) {
100134 if ( $ key > $ number ) {
101135 $ next = $ key ;
@@ -104,10 +138,12 @@ function closest( $array, $number ) {
104138 $ prev = $ key ;
105139 }
106140
141+ // if the number is closer to the next roman number (IV closer to V)
107142 if ( $ number / $ next >= $ ratio ) {
108143 return $ next ;
109144 }
110145
146+ // number was closer to previous number (II, III closer to I)
111147 return $ prev ;
112148 }
113149}
0 commit comments