Romeinse cijfers

Gepost door Jan Willem van der Veer op 02-11-2010 00:23.

Ondanks dat er al twee scripts in de library staan die iets met romeinse cijfers te doen hebbe, hier nog één. Ik denk dat deze wat toevoegd aan de SL om de volgende redenen:
1. Het is een two-way-script. Dus zowel van Romeinse cijfers naar decimale cijfers als van decimale naar Romeinse cijfers. Met checks voor de validiteit van romeinse cijfers.
2. De code is efficiënter.
3. Het bereik van deze scripts is hoger (en wel tot de officiële 3.888.888 die je maximaal met romeinse cijfers kunt halen).
4. Het is OOP (ligt er een beetje aan wat je daaronder verstaat).

Feedback is welkom. Suggesties ter verdere verbetering ook. Negatief commentaar mag je uitdrukken in een cijfer.

Ter vergelijking een verwijzing naar de andere scripts met vergelijkbare functionaliteit op deze site:
- http://www.pfz.nl/scripts/90-romeinse-cijfers-omzetten/
- http://www.pfz.nl/scripts/42-romeinse-cijfers/

Bestanden van dit script

romanNumerals.class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
<?php
error_reporting(E_ALL);

class RomanNumerals {
    /* Officially the numbers > 1000 should be overlined.
     * In unicode there are several codepoints for this purpose.
     * But for compatibility with ISO-8859 I used lowercase characters (more often used for this trick).
     * See for example: http://jeankorte.ca/jk-roman-numeral-converter.html
     */
    static private $asRomanTransTable = Array(
        1e6 => 'm',
        9e5 => 'cm',
        5e5 => 'd',
        4e5 => 'cd',
        1e5 => 'c',
        9e4 => 'xc',
        5e4 => 'l',
        4e4 => 'xl',
        1e4 => 'x',
        9e3 => 'ix',
        5e3 => 'v',
        4e3 => 'iv',
        1e3 => 'M',
        900 => 'CM',
        500 => 'D',
        400 => 'CD',
        100 => 'C',
        90 => 'XC',
        50 => 'L',
        40 => 'XL',
        10 => 'X',
        9 => 'IX',
        5 => 'V',
        4 => 'IV',
        1 => 'I'
    );

    /**
     * From decimal to Roman system.
     *
     * @param   integer $iDecimal
     * @return  string              The given decimal in Roman numeral system.
     */
    public static function toRoman($iDecimal) {
        # There is no Roman numeral representation below 1, and 3,999,999 is the longest number represented by Roman numerals
        $decimalValue = (int) $iDecimal;
        if (($iDecimal < 1) || ($iDecimal <= 0) || ($iDecimal > 3999999)) return NULL;

        $sRoman = '';
        foreach(self::$asRomanTransTable as $iNum => $sSymbol){
            if ($iDecimal < 1) break;
            while ($iDecimal >= $iNum){
                $iDecimal -= $iNum;
                $sRoman .= $sSymbol;
            }
        }
        return $sRoman;
    }

    /**
      * From roman to decimal numeral system
      *
      * @param    string    $sRoman
      * @return    integer                   0 on failure.
      */
    public static function toDecimal($sRoman){
        if(!is_string($sRoman)) return 0;

        $iStrLen = strlen($sRoman);
        $iDoubleSymbol = $iDec = $iPos = 0;
        foreach(self::$asRomanTransTable as $iNum => $sSymbol){
            $iLen = strlen($sSymbol);
            $iCount = 0;
            if($iDoubleSymbol){
                --$iDoubleSymbol;
                continue;
            }

            # Mind the fact that 1000 in the Roman numeral system may be represented by M or i.
            while((($sChunk = substr($sRoman, $iPos, $iLen)) == $sSymbol) || (($iNum < 1e4) && ($sChunk == strtr($sSymbol, 'iM', 'Mi')))){
                if($iLen == 2) $iDoubleSymbol = 3 - 2*($iNum % 3);
                $iDec += $iNum;
                $iPos += $iLen;

                # All symbols that represent 1eX may appear at maximum three times. All other symbols may only represent one time in a roman number.
                if(fmod(log10($iNum), 1) || (++$iCount == 3)) break;
            }
            if($iPos == $iStrLen) break;
        }

        # If there are symbols left, then the number was mallformed (following default rules (M = 1000 and i = 1000)).
        return (($iPos == $iStrLen)? $iDec : 0);
    }
}


define('NUMBS','3809818
1351971
2897170
1789491
3794541
1923748
2989434
985486
2656459
769272');

/**
* Result should be:
Mind the fact that i = M!

mmmdcccMxDCCCXVIII
mccclMCMLXXI
mmdcccxcvMMCLXX
mdcclxxxMxCDXCI
mmmdccxcMvDXLI
mcmxxMMMDCCXLVIII
mmcmlxxxMxCDXXXIV
cmlxxxvCDLXXXVI
mmdclvMCDLIX
dcclxMxCCLXXII

equals (script output, following wikipedian rules):
mmmdcccixDCCCXVIII
mccclMCMLXXI
mmdcccxcvMMCLXX
mdcclxxxixCDXCI
mmmdccxcivDXLI
mcmxxMMMDCCXLVIII
mmcmlxxxixCDXXXIV
cmlxxxvCDLXXXVI
mmdclvMCDLIX
dcclxixCCLXXII

even equals:
mmmdcccixDCCCXVIII
mcccliCiLXXI
mmdcccxcviiCLXX
mdcclxxxixCDXCI
mmmdccxcivDXLI
mcmxxiiiDCCXLVIII
mmcmlxxxixCDXXXIV
cmlxxxvCDLXXXVI
mmdclviCDLIX
dcclxixCCLXXII
*/

foreach (explode("\n", NUMBS) as $numb) {
    echo RomanNumerals::toRoman($numb)."\n";
}
echo "\n";

define('ROMAN', 'mmmdcccixDCCCXVIII
mccclMCMLXXI
mmdcccxcvMMCLXX
mdcclxxxixCDXCI
mmmdccxcivDXLI
mcmxxMMMDCCXLVIII
mmcmlxxxixCDXXXIV
cmlxxxvCDLXXXVI
mmdclvMCDLIX
dcclxixCCLXXII');

/**
 * Result should be:
3809818
1351971
2897170
1789491
3794541
1923748
2989434
985486
2656459
769272
 * 
 */

foreach (explode("\n", ROMAN) as $numb) {
    echo RomanNumerals::toDecimal(trim($numb))."\n";
}
echo "\n";

define('ROMAN1', 'mmmdcccMxDCCCXVIII
mccclMCMLXXI
mmdcccxcvMMCLXX
mdcclxxxMxCDXCI
mmmdccxcMvDXLI
mcmxxMMMDCCXLVIII
mmcmlxxxMxCDXXXIV
cmlxxxvCDLXXXVI
mmdclvMCDLIX
dcclxMxCCLXXII');

/**
 * Result should be the same as previous example...
 * 
 */

foreach (explode("\n", ROMAN1) as $numb) {
    echo RomanNumerals::toDecimal(trim($numb))."\n";
}

/**
 * The biggest and highest Roman numbers allowed.
 * Result:
3888888
3999999
 *
 */
echo RomanNumerals::toDecimal('mmmdccclxxxvMMMDCCCLXXXVIII')."\n";
echo RomanNumerals::toDecimal('mmmcmxcMxCMXCIX')."\n";

/**
 * Some invalid roman numbers...
 *
 * Should all return zero or NULL.
 *
 */
echo RomanNumerals::toDecimal('mmmmdccclxxxvMMMDCCCLXXXVIII')."\n"; # Too many m's.
echo RomanNumerals::toDecimal('mmmcdclxxxvMMMDCCCLXXXVIII')."\n"; # cdc forbidden... cdc should be only d.
echo RomanNumerals::toDecimal('mmmdccclxxxvMMMDXCLVIII')."\n"; # XCL = 140, should be coded as CXL
echo RomanNumerals::toDecimal('mmmdccclxxxvMMMDCLLVIII')."\n"; # LL = 100, should be coded as C.
echo RomanNumerals::toRoman(4e6);

?>

Commentaar

07-11-2010 14:43

Mooi en net script.

Goede documentatie en zeer uitgebreide testcases.
Deze gaat in m'n verzameling! Kan ik nog vaker gebruiken namelijk :)

08-11-2010 14:57

In de uitleg staat er dat het maximaal haalbare 3.888.888 is terwijl in de code 3.999.999 staat.

08-11-2010 22:36

Die 3.888.888 moet 3.999.999 zijn. Maar zolang er geen mogelijkheid is om scripts te editen, blijft dit er zo staan.

3.888.888 is het langste Romeinse getal wat je kunt maken. 3.999.999 is het hoogste getal wat je met de Romeinse notatie aankan.

Inloggen wachtwoord vergeten? Aanmelden