From a08415a7b5190fb90e6fc042a094ace2e17e4d32 Mon Sep 17 00:00:00 2001 From: Paul Kievits Date: Fri, 6 Mar 2020 10:34:51 +0100 Subject: [PATCH] Improved the ARABIC function to handle short-form roman numerals --- CHANGELOG.md | 4 ++ src/PhpSpreadsheet/Calculation/MathTrig.php | 66 ++++++++++++++------- tests/data/Calculation/MathTrig/ARABIC.php | 24 ++++++++ 3 files changed, 73 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcbe3532..16313206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com) and this project adheres to [Semantic Versioning](https://semver.org). +## [Unreleased] + +- Improved the ARABIC function to also hande short-hand roman numerals + ## [1.11.0] - 2020-03-02 ### Added diff --git a/src/PhpSpreadsheet/Calculation/MathTrig.php b/src/PhpSpreadsheet/Calculation/MathTrig.php index 33bfee19..51f3b899 100644 --- a/src/PhpSpreadsheet/Calculation/MathTrig.php +++ b/src/PhpSpreadsheet/Calculation/MathTrig.php @@ -61,32 +61,15 @@ class MathTrig } // Convert the roman numeral to an arabic number - $lookup = [ - 'M' => 1000, 'CM' => 900, - 'D' => 500, 'CD' => 400, - 'C' => 100, 'XC' => 90, - 'L' => 50, 'XL' => 40, - 'X' => 10, 'IX' => 9, - 'V' => 5, 'IV' => 4, 'I' => 1, - ]; - $negativeNumber = $roman[0] === '-'; if ($negativeNumber) { $roman = substr($roman, 1); } - $arabic = 0; - for ($i = 0; $i < strlen($roman); ++$i) { - if (!isset($lookup[$roman[$i]])) { - return Functions::VALUE(); // Invalid character detected - } - - if ($i < (strlen($roman) - 1) && isset($lookup[substr($roman, $i, 2)])) { - $arabic += $lookup[substr($roman, $i, 2)]; // Detected a match on the next 2 characters - ++$i; - } else { - $arabic += $lookup[$roman[$i]]; // Detected a match on one character only - } + try { + $arabic = self::calculateArabic(str_split($roman)); + } catch (\Exception $e) { + return Functions::VALUE(); // Invalid character detected } if ($negativeNumber) { @@ -96,6 +79,47 @@ class MathTrig return $arabic; } + /** + * Recursively calculate the arabic value of a roman numeral. + * + * @param array $roman + * @param int $sum + * @param int $subtract + * + * @return int + */ + protected static function calculateArabic(array $roman, &$sum = 0, $subtract = 0) + { + $lookup = [ + 'M' => 1000, + 'D' => 500, + 'C' => 100, + 'L' => 50, + 'X' => 10, + 'V' => 5, + 'I' => 1, + ]; + + $numeral = array_shift($roman); + if (!isset($lookup[$numeral])) { + throw new \Exception('Invalid character detected'); + } + + $arabic = $lookup[$numeral]; + if (count($roman) > 0 && isset($lookup[$roman[0]]) && $arabic < $lookup[$roman[0]]) { + $subtract += $arabic; + } else { + $sum += ($arabic - $subtract); + $subtract = 0; + } + + if (count($roman) > 0) { + self::calculateArabic($roman, $sum, $subtract); + } + + return $sum; + } + /** * ATAN2. * diff --git a/tests/data/Calculation/MathTrig/ARABIC.php b/tests/data/Calculation/MathTrig/ARABIC.php index 3c397dc9..c4773beb 100644 --- a/tests/data/Calculation/MathTrig/ARABIC.php +++ b/tests/data/Calculation/MathTrig/ARABIC.php @@ -33,4 +33,28 @@ return [ -2018, '-MMXVIII', ], + [ + 499, + 'CDXCIX' + ], + [ + 499, + 'LDVLIV' + ], + [ + 499, + 'XDIX' + ], + [ + 499, + 'VDIV' + ], + [ + 499, + 'ID' + ], + [ + '#VALUE!', + 'WRONG' + ], ];