Numbers to Roman Numbers with php
Asked Answered
L

9

39

I need to transform ordinary numbers to Roman numerals with php and I have this code:

        <?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);


?>

try this way but does not work:

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

the problem is that I need to change numbers are readings of my sql database and do not know how to, from and through.

Livvi answered 21/2, 2013 at 5:6 Comment(4)
Possible duplicate: #267851Drum
Oh your title is backwards. Here you go: #6266096Drum
Number To Roman in PHPEscritoire
mkaatman this page is the inverse of my problem!Livvi
M
96

I found this code here: 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;
}
Mediatize answered 22/2, 2013 at 11:48 Comment(3)
but I need to transform a reading MYSQL and this code does not work: echo romanic_number(. $row['id'].); I'm sorry, is not handling well the php yet.Livvi
No, the problem is not the function. I suggest you learn how to handle variables within functions and arrays, you need to say ...".echo romanic_number($row_id)."...Mediatize
I added the end-tag for php and implemented the $upcase supportCompeer
A
6

Another way to do that

<?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); 
?>
Andrey answered 10/10, 2014 at 11:51 Comment(1)
This answer can be boiled down a little farther by eliminating single use variables, using type casting instead of a function call, and using a combined assignment operator. 3v4l.org/LgGajRidgway
L
6

You can format integers into Roman numeral symbols with the ICU intl library's NumberFormatter class by setting the locale parameter to @numbers=roman with decimal style format:

function intToRomanNumeral(int $num) {
    static $nf = new NumberFormatter('@numbers=roman', NumberFormatter::DECIMAL);
    return $nf->format($num);
}

Output examples:

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); // ↂ

Alternatively, you can use the MessageFormatter class, but in my own testing it appears to have significantly lower performance than NumberFormatter:

function intToRomanNumeral(int $num) {
    static $nf = new MessageFormatter('@numbers=roman', '{0, number}');
    return $nf->format([$num]);
}
Lippe answered 12/2, 2023 at 18:5 Comment(0)
P
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
Palila answered 20/10, 2014 at 8:44 Comment(6)
This seems to work, but warns: Illegal offset at ''. I've put @$c[... and it seems to be perfect.Unijugate
@Vojtěch that turns off error reporting, of course it'll look perfect.Usk
Works with 1, 2, 3 and then crashes on 'A non-numeric value encountered' for $N = 4, tested with PHP 7.3.Ethelind
This attempt at codegolf is missing its educational explanation and the snippet itself is not easy to read with the obvious deficit of spaces.Ridgway
This snippet generates Errors/Warnings/Notices/Deprecations as of PHP7.4. 3v4l.org/P98ulRidgway
There is an illegal offset issue. Replace $o=1:$b].$s); with $o=1:$b >0 ? $b : 0].$s);Chavez
S
1

CHECKED AND VERIFIED BY PHP UNIT

Make a class having name RomanNumerials and add a protected static property as defined:

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',
    ];

then add a method as follows

public function output ($number)
    {
        $solution = '';

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

        return $solution;
    }
Stefanistefania answered 3/5, 2016 at 12:13 Comment(1)
I adopted the same, fastest method of all the ones on this topic. Timed with loops from 1 to 20000, the str_repeat method is much slower, and the other 'while' method is slightly slower because of the $map spanning restart and local allocation. I couldn't time the oneliner from Jasiek above as it doesn't work with PHP 7.3 and this is code I won't/can't read/debug.Ethelind
S
0

I improve rome() function of Jasiek

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;
}
Stoeber answered 17/7, 2021 at 18:35 Comment(1)
This unexplained snippet requires explicit int casting to work from PHP7.4 3v4l.org/HnMGORidgway
S
0

My own function it has the best performance:

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;
}
Stoeber answered 22/7, 2021 at 7:10 Comment(1)
This verbose/inelegant answer is missing its educational explanation.Ridgway
R
0

While appreciate the succinct mathematical nested loop approach posted by @user2095686, I agree more with @HaiderLasani's approach of writing the foreach() as the outer loop because it never unnecessarily revisits previously processed elements in the translation array. Haider's answer does not explain why this adjustment is ideal. I've created a demo to echo out all of the unnecessary conditional checks that @user2095686's answer makes. Gone one step further than @Haider's answer, I've written a condition inside of the while() loop to break both loops as soon as the modified input integer is reduced to zero.

Code: (Demo)

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;
}

If you are not turned off by the style of performing assignment arithmetic inside of a conditional expression, the script can be condensed a little more...

foreach ($conversions as $int => $roman) {
    while ($integer >= $int) {
        $romanString .= $roman;
        if (!($integer -= $int)) {
            break 2;
        }
    }
} 
Ridgway answered 22/9, 2022 at 3:58 Comment(0)
P
-4

Check out my solution here https://github.com/frostymarvelous/Whisppa-Libs/blob/master/Misc/Numeralo.php . It works both ways.

    <?php
     /**
     * @package    Whisppa
     * @subpackage Misc
     * @license    http://opensource.org/licenses/MIT  MIT License
     * @author     Stefan (frostymarvelous) Froelich <[email protected]>
     * @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;

        }


    }
Paratroops answered 16/4, 2015 at 23:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.