使用php将数字转换为罗马数字

38

我需要使用PHP将普通数字转换为罗马数字,我有以下代码:

        <?php

function roman2number($roman){
    $conv = array(
        array("letter" => 'I', "number" => 1),
        array("letter" => 'V', "number" => 5),
        array("letter" => 'X', "number" => 10),
        array("letter" => 'L', "number" => 50),
        array("letter" => 'C', "number" => 100),
        array("letter" => 'D', "number" => 500),
        array("letter" => 'M', "number" => 1000),
        array("letter" => 0, "number" => 0)
    );
    $arabic = 0;
    $state = 0;
    $sidx = 0;
    $len = strlen($roman);

    while ($len >= 0) {
        $i = 0;
        $sidx = $len;

        while ($conv[$i]['number'] > 0) {
            if (strtoupper(@$roman[$sidx]) == $conv[$i]['letter']) {
                if ($state > $conv[$i]['number']) {
                    $arabic -= $conv[$i]['number'];
                } else {
                    $arabic += $conv[$i]['number'];
                    $state = $conv[$i]['number'];
                }
            }
            $i++;
        }

        $len--;
    }

    return($arabic);
}


function number2roman($num,$isUpper=true) {
    $n = intval($num);
    $res = '';

    /*** roman_numerals array ***/
    $roman_numerals = array(
        '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
    );

    foreach ($roman_numerals as $roman => $number)
    {
        /*** divide to get matches ***/
        $matches = intval($n / $number);

        /*** assign the roman char * $matches ***/
        $res .= str_repeat($roman, $matches);

        /*** substract from the number ***/
        $n = $n % $number;
    }

    /*** return the res ***/
    if($isUpper) return $res;
    else return strtolower($res);
}

/* TEST */
echo $s=number2roman(6,true);
echo "\n and bacK:\n";
echo roman2number($s);


?>

尝试了这种方式,但并没有起作用:

echo $s=number2roman((.$row['id'].),true);
echo "\n and bacK:\n";
echo roman2number($s);

问题是我需要改变我的SQL数据库中的数字读数,但我不知道如何做到从哪里开始,到哪里结束。


可能是重复的问题:https://dev59.com/DkXRa4cB1Zd3GeqPt7RT?rq=1 - Matt
哦,你的标题反了。这是链接:https://dev59.com/im025IYBdhLWcg3wAxB9 - Matt
5
将数字转换为罗马数字的 PHP 代码实现 - Antony
mkaatman这个页面正好与我的问题相反! - LukasMakey
9个回答

93

我在这里找到了这段代码:http://php.net/manual/en/function.base-convert.php

Optimized and prettified function:

/**
 * @param int $number
 * @return string
 */
function numberToRomanRepresentation($number) {
    $map = array('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);
    $returnValue = '';
    while ($number > 0) {
        foreach ($map as $roman => $int) {
            if($number >= $int) {
                $number -= $int;
                $returnValue .= $roman;
                break;
            }
        }
    }
    return $returnValue;
}


但我需要转换一个读取MYSQL的罗马数字,而这段代码不起作用: echo romanic_number(. $row['id'].); 很抱歉,PHP还没有处理好。 - LukasMakey
6
不,问题并不在函数上。我建议你学习如何在函数和数组内处理变量,你需要说...".echo romanic_number($row_id)."... - user2095686
我添加了 PHP 的结束标签并实现了 $upcase 支持。 - Joeran

6

另一种方法来实现这个

<?php 
function ConverToRoman($num){ 
    $n = intval($num); 
    $res = ''; 

    //array of roman numbers
    $romanNumber_Array = array( 
        '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); 

    foreach ($romanNumber_Array as $roman => $number){ 
        //divide to get  matches
        $matches = intval($n / $number); 

        //assign the roman char * $matches
        $res .= str_repeat($roman, $matches); 

        //substract from the number
        $n = $n % $number; 
    } 

    // return the result
    return $res; 
} 

echo ConverToRoman(23); 
?>

这个答案可以进一步简化,通过消除单一使用的变量,使用类型转换而不是函数调用,并使用组合赋值运算符。https://3v4l.org/LgGaj - mickmackusa

4
function rome($N){
    $c='IVXLCDM';
    for($a=5,$b=$s='';$N;$b++,$a^=7)
        for($o=$N%$a,$N=$N/$a^0;$o--;$s=$c[$o>2?$b+$N-($N&=-2)+$o=1:$b].$s);
    return $s;
}

// from polish wiki

1
这似乎可以工作,但是会警告:Illegal offset at ''。我已经加上了@$c[...,现在看起来完美无缺。 - Vojtěch
1
@Vojtěch 关闭错误报告,当然它看起来会完美无缺 - user3714134
适用于1、2、3,但在$N = 4时遇到“遇到非数字值”的错误而崩溃,已在PHP 7.3下测试。 - Francis Pierot
这个代码高尔夫尝试缺少教育性的解释,而且片段本身没有明显的空格,难以阅读。 - mickmackusa
该代码片段在PHP7.4中会生成错误/警告/通知/弃用警告。https://3v4l.org/P98ul - mickmackusa
存在非法偏移问题。请使用以下代码替换:$o=1:$b >0 ? $b : 0].$s); - Reto

3
您可以使用 ICU 的 intl 库的 NumberFormatter 类,通过将 locale 参数设置为 @numbers=roman 以十进制格式进行,将整数格式化为罗马数字符号:
function intToRomanNumeral(int $num) {
    static $nf = new NumberFormatter('@numbers=roman', NumberFormatter::DECIMAL);
    return $nf->format($num);
}

输出示例:

echo intToRomanNumeral(2); // II

echo intToRomanNumeral(5); // V

echo intToRomanNumeral(10); // X

echo intToRomanNumeral(50); // L

echo intToRomanNumeral(57); // LVII
echo intToRomanNumeral(58); // LVIII

echo intToRomanNumeral(100); // C

echo intToRomanNumeral(150); // CL

echo intToRomanNumeral(1000); // M

echo intToRomanNumeral(10000); // ↂ

或者,您可以使用MessageFormatter类,但在我自己的测试中,它似乎比NumberFormatter具有明显更低的性能:

function intToRomanNumeral(int $num) {
    static $nf = new MessageFormatter('@numbers=roman', '{0, number}');
    return $nf->format([$num]);
}

1

已由PHP Unit验证和核实

创建一个名为RomanNumerials的类,并添加一个定义为protected static的属性:

protected static $lookup = [
        1000 => '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',
    ];

然后添加一个如下的方法。
public function output ($number)
    {
        $solution = '';

        foreach(static::$lookup as $limit => $glyph){
            while ($number >= $limit) {
                $solution .= $glyph;
                $number -= $limit;
            }
        }

        return $solution;
    }

我采用了这个主题中所有最快的方法。通过循环计时从1到20000,str_repeat方法要慢得多,而另一个“while”方法稍微慢一些,因为$map跨越重启和本地分配。我无法计时Jasiek上面的一行代码,因为它不适用于PHP 7.3,而这是我不能/不想阅读/调试的代码。 - Francis Pierot

0
虽然感谢@user2095686发布的简洁的数学嵌套循环方法,但我更赞同@HaiderLasani的做法,即将foreach()作为外层循环编写,因为它不会不必要地重新访问翻译数组中先前处理过的元素。Haider的答案没有解释这种调整的理想之处。我创建了一个演示来回显@user2095686的答案所进行的所有不必要的条件检查。比@Haider的答案更进一步,我在while()循环内部编写了一个条件,以便在修改后的输入整数被减少到零时立即终止两个循环。
代码:(演示
function numberToRoman(int $integer): string {
    static $conversions = [
        1000 => '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'
    ];
    $romanString = '';
    foreach ($conversions as $int => $roman) {
        while ($integer >= $int) {
            $integer -= $int;
            $romanString .= $roman;
            if (!$integer) {
                break 2;
            }
        }
    }
    return $romanString;
}

如果你不介意在条件表达式中执行赋值算术的风格,那么脚本可以更加简洁...

foreach ($conversions as $int => $roman) {
    while ($integer >= $int) {
        $romanString .= $roman;
        if (!($integer -= $int)) {
            break 2;
        }
    }
} 

0

我的自定义函数具有最佳性能:

function romanNumber($n)
{
    // support for numbers greater than a thousand
    $ret1 = '';
    while ($n >= 1000) {
        $ret1 .= 'M';
        $n -= 1000;
    }

    $ret = '';
    if ($n > 0) {
        $n = (string) $n;
        $l = 'IVXLCDM';
        $j = 0; // goes by roman letters
        for ($i = strlen($n)-1; $i >= 0; --$i) { // goes by decimal number
            switch ($n[$i]) {
                case 0: $s = ''; break;
                case 1: $s = $l[$j]; break;
                case 2: $s = $l[$j].$l[$j]; break;
                case 3: $s = $l[$j].$l[$j].$l[$j]; break;
                case 4: $s = $l[$j].$l[$j+1]; break;
                case 5: $s = $l[$j+1]; break;
                case 6: $s = $l[$j+1].$l[$j]; break;
                case 7: $s = $l[$j+1].$l[$j].$l[$j]; break;
                case 8: $s = $l[$j+1].$l[$j].$l[$j].$l[$j]; break;
                case 9: $s = $l[$j].$l[$j+2]; break;
            }
            $j += 2;
            $ret = $s.$ret;
        }
    }

    return $ret1.$ret;
}

这个啰嗦/不优雅的答案缺少了它的教育解释。 - mickmackusa

0
我改进了Jasiek的rome()函数。
function rome2($N)
{
    // support for numbers greater than a thousand
    $ss = '';
    while ($N > 1000) {
        $ss .= 'M';
        $N -= 1000;
    }

    $c = 'IVXLCDM';
    for ($a = 5, $b = 0, $s = ''; $N; $b++, $a ^= 7)
        for ($o = $N % $a, $N = $N / $a ^ 0; $o--; ) {
            $s = $c[$o > 2 ? $b + $N - ($N &= -2) + $o = 1 : $b] . $s;
        }
    return $ss.$s;
}

这个未解释的代码片段需要在PHP7.4中进行显式的int转换才能工作。https://3v4l.org/HnMGO - mickmackusa

-4

请查看我的解决方案https://github.com/frostymarvelous/Whisppa-Libs/blob/master/Misc/Numeralo.php。它可以双向工作。

    <?php
     /**
     * @package    Whisppa
     * @subpackage Misc
     * @license    http://opensource.org/licenses/MIT  MIT License
     * @author     Stefan (frostymarvelous) Froelich <sfroelich@gmail.com>
     * @copyright  Copyright (c) 2015, Stefan (frostymarvelous) Froelich
     */
    namespace Whisppa\Misc;


    /**
     * This class allows you to convert from Roman numerals to natural numbers and vice versa.
     * I decided to do this as a fun challenge after reading http://thedailywtf.com/articles/Roman-Enumeration
     * Took me about 30 minutes to come up with, research and code the solution.
     * It can convert numbers up to 3,999,999 because I couldn't find any numerals for 5,000,000 above.
     * Due to my inability to get the correct accented characters 5000 above, I resulted to using the pipe (|) to represent accent. 
     */
    class Numeralo
    {
        /**
        * @var string[] A notation map to represent the common Roman numeral values. 
        * @static
        */
        protected static $NOTATION = 
        [
            '|', //one
            '[', //five
            ']', //ten
        ];

        /**
        * @var \ArrayObject[] A map of Roman numerals based on place value. Each item ends with the first numeral in the next place value.
        * @static
        */
        protected static $NUMERALS_BY_PLACE_VALUE =     
        [
            ['I', 'V', 'X',], //ones
            ['X', 'L', 'C',], //tens
            ['C', 'D', 'M',], // hundreds
            ['M', 'V|', 'X|',], //thousands
            ['X|', 'L|', 'C|',], //tens of thousands
            ['C|', 'D|', 'M|',], //hundreds of thousands
            ['M|', '~', '~',], // millions. there are no values for the last two that I could find
        ];

        /**
        * @var string[]  sA map of numbers and their representative Roman numerals in notation format. This map allows us to make any numeral by replacing the the notation with the place value equivalent.    
        * @static
        */
        protected static $NUMBER_TO_NOTATION = 
        [
            '0' => '',
            '1' => '|',
            '2' => '||',
            '3' => '|||',
            '4' => '|[',
            '5' => '[',
            '6' => '[|',
            '7' => '[||',
            '8' => '[|||',
            '9' => '|]',
        ];

        /**
        * @var int[] A map of the major Roman numerals and the number equivalent.
        * @static
        */
        protected static $NUMERALS_TO_NUMBER = 
        [
            'I' => 1,
            'V' => 5,
            'X' => 10,
            'L' => 50,
            'C' => 100,
            'D' => 500,
            'M' => 1000,
            'V|' => 5000,
            'X|' => 10000,
            'L|' => 50000,
            'C|' => 100000,
            'D|' => 500000,
            'M|' => 1000000,
        ];

        /**
        * Converts natural numbers to Roman numerals.
        *
        * @static
        * @param int|string $number a number or numeric string less than 3,999,999
        * @throws \InvalidArgumentException if the provided $number argument is not numeric or greater than 3,999,999.
        * @return string Roman numeral equivalent   
        */
        public static function number_to_numerals($number) {
            if(!is_numeric($number))
                throw new \InvalidArgumentException('Only numbers allowed');
            if($number > 3999999)
                throw new \InvalidArgumentException('Number cannot be greater than 3,999,999');


            $numerals = '';
            $number_string = strrev((string) $number);
            $length = strlen($number_string);

            for($i = 0; $i < $length; $i++) {
                $char = $number_string[$i];

                $num_map = self::$NUMERALS_BY_PLACE_VALUE[$i];
                $numerals = str_replace(self::$NOTATION, $num_map, self::$NUMBER_TO_NOTATION[$char]) . $numerals;
            }

            return $numerals;   
        }

        /**
        * Converts Roman numerals to natural numbers.
        *
        * @static
        * @param string $numerals the Roman numerals to be converted
        * @throws \InvalidArgumentException if the provided $numerals argument contains invalid characters.
        * @return int the equivalent number
        */
        public static function numerals_to_number($numerals) {
            $number = 0;
            $numeral_string = strrev((string) $numerals);
            $length = strlen($numeral_string);

            $prev_number = false;
            $is_accented = false;

            for($i = 0; $i < $length; $i++) {
                $char = $numeral_string[$i];

                if($char == '|') //check if it is an accent character
                {
                    $is_accented = true;
                    continue;//skip this iteration and process it in the next one as the accent applies to the next char
                }
                else if($is_accented)
                {
                    $char .= '|';
                    $is_accented = false;
                }

                //TODO Make a check using maybe regex at the beginning of the method.
                if(!isset(self::$NUMERALS_TO_NUMBER[$char]))
                    throw new \InvalidArgumentException("Invalid character '{$char}' in numeral string");


                $num = self::$NUMERALS_TO_NUMBER[$char];

                //this is where the magic happens
                //if the previous number divided by 5 or 10 is equal to the current number, then we subtract eg. 9 = IX. I = 1, X = 10, 10/10 = 1
                if($prev_number)
                {
                    if(($prev_number / 5) == $num || ($prev_number / 10) == $num)
                        $number -= $num;
                    else
                        $number += $num;
                }
                else
                    $number += $num;


                $prev_number = $num;
            }

            return $number;

        }


    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接