1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace PhpOffice\PhpSpreadsheet\Calculation\MathTrig; |
4
|
|
|
|
5
|
|
|
use PhpOffice\PhpSpreadsheet\Calculation\ArrayEnabled; |
6
|
|
|
use PhpOffice\PhpSpreadsheet\Calculation\Exception; |
7
|
|
|
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError; |
8
|
|
|
|
9
|
|
|
class Arabic |
10
|
|
|
{ |
11
|
|
|
use ArrayEnabled; |
12
|
|
|
|
13
|
|
|
private const ROMAN_LOOKUP = [ |
14
|
|
|
'M' => 1000, |
15
|
|
|
'D' => 500, |
16
|
|
|
'C' => 100, |
17
|
|
|
'L' => 50, |
18
|
|
|
'X' => 10, |
19
|
|
|
'V' => 5, |
20
|
|
|
'I' => 1, |
21
|
|
|
]; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Recursively calculate the arabic value of a roman numeral. |
25
|
|
|
*/ |
26
|
16 |
|
private static function calculateArabic(array $roman, int &$sum = 0, int $subtract = 0): int |
27
|
|
|
{ |
28
|
16 |
|
$numeral = array_shift($roman); |
29
|
16 |
|
if (!isset(self::ROMAN_LOOKUP[$numeral])) { |
30
|
1 |
|
throw new Exception('Invalid character detected'); |
31
|
|
|
} |
32
|
|
|
|
33
|
15 |
|
$arabic = self::ROMAN_LOOKUP[$numeral]; |
34
|
15 |
|
if (count($roman) > 0 && isset(self::ROMAN_LOOKUP[$roman[0]]) && $arabic < self::ROMAN_LOOKUP[$roman[0]]) { |
35
|
11 |
|
$subtract += $arabic; |
36
|
|
|
} else { |
37
|
15 |
|
$sum += ($arabic - $subtract); |
38
|
15 |
|
$subtract = 0; |
39
|
|
|
} |
40
|
|
|
|
41
|
15 |
|
if (count($roman) > 0) { |
42
|
14 |
|
self::calculateArabic($roman, $sum, $subtract); |
43
|
|
|
} |
44
|
|
|
|
45
|
15 |
|
return $sum; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* ARABIC. |
50
|
|
|
* |
51
|
|
|
* Converts a Roman numeral to an Arabic numeral. |
52
|
|
|
* |
53
|
|
|
* Excel Function: |
54
|
|
|
* ARABIC(text) |
55
|
|
|
* |
56
|
|
|
* @param string|string[] $roman Should be a string, or can be an array of strings |
57
|
|
|
* |
58
|
|
|
* @return array|int|string the arabic numberal contrived from the roman numeral |
59
|
|
|
* If an array of numbers is passed as the argument, then the returned result will also be an array |
60
|
|
|
* with the same dimensions |
61
|
|
|
*/ |
62
|
17 |
|
public static function evaluate(mixed $roman): array|int|string |
63
|
|
|
{ |
64
|
17 |
|
if (is_array($roman)) { |
65
|
17 |
|
return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $roman); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
// An empty string should return 0 |
69
|
17 |
|
$roman = substr(trim(strtoupper((string) $roman)), 0, 255); |
70
|
17 |
|
if ($roman === '') { |
71
|
1 |
|
return 0; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
// Convert the roman numeral to an arabic number |
75
|
16 |
|
$negativeNumber = $roman[0] === '-'; |
76
|
16 |
|
if ($negativeNumber) { |
77
|
2 |
|
$roman = trim(substr($roman, 1)); |
78
|
|
|
if ($roman === '') { |
79
|
|
|
return ExcelError::NAN(); |
80
|
|
|
} |
81
|
16 |
|
} |
82
|
1 |
|
|
83
|
1 |
|
try { |
84
|
|
|
$arabic = self::calculateArabic(mb_str_split($roman, 1, 'UTF-8')); |
85
|
|
|
} catch (Exception) { |
86
|
15 |
|
return ExcelError::VALUE(); // Invalid character detected |
87
|
2 |
|
} |
88
|
|
|
|
89
|
|
|
if ($negativeNumber) { |
90
|
15 |
|
$arabic *= -1; // The number should be negative |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
return $arabic; |
94
|
|
|
} |
95
|
|
|
} |
96
|
|
|
|