如何将罗马数字转换为十进制数字?

时间:2018-11-13 17:42:37

标签: oracle plsql

这会将十进制数字转换为罗马数字:

select  to_char(515, 'RN') from dual;

返回:DXV

如何做相反的事情?这引发了ORA-01722: Invalid number

select to_number('DXV', 'RN') from dual;

4 个答案:

答案 0 :(得分:2)

只是为了好玩,另一种方法是将字符串分成单个和相邻数字组,允许使用标准减法表示法(谢谢Wikipedia;将每个单个或相邻对或数字转换为它们的十进制等效项;然后对它们求和:

with t (str) as (select 'MCMLXXXIV' from dual)
select sum(
  case regexp_substr(str, '(CM|M|CD|D|XC|C|XL|L|IX|X|IV|V|I)', 1, level)
    when 'M'  then 1000
    when 'CM' then 900
    when 'D'  then 500
    when 'CD' then 400
    when 'C'  then 100
    when 'XC' then 90
    when 'L'  then 50
    when 'XL' then 40
    when 'X'  then 10
    when 'IX' then 9
    when 'V'  then 5
    when 'IV' then 4
    when 'I'  then 1
  end) as decimals
from t
connect by regexp_substr(str, '(CM|M|CD|D|XC|C|XL|L|IX|X|IV|V|I)', 1, level) is not null;

  DECIMALS
----------
      1984

请注意,正则表达式中搜索词的顺序与其等效的十进制顺序不同;您需要将M之前的CM匹配为减号。

如果这是您需要做的事情,那么可能值得创建一个确定性函数(当然,对于递归CTE方法也是如此)。在这种情况下,您可以切换到PL / SQL循环以减少上下文切换:

create or replace function roman_to_decimal(p_roman varchar2)
return number deterministic is
  l_decimal number := 0;
begin
  for i in 1..regexp_count(p_roman, '(CM|M|CD|D|XC|C|XL|L|IX|X|IV|V|I)') loop
    l_decimal := l_decimal +
      case regexp_substr(p_roman, '(CM|M|CD|D|XC|C|XL|L|IX|X|IV|V|I)', 1, i)
        when 'M'  then 1000
        when 'CM' then 900
        when 'D'  then 500
        when 'CD' then 400
        when 'C'  then 100
        when 'XC' then 90
        when 'L'  then 50
        when 'XL' then 40
        when 'X'  then 10
        when 'IX' then 9
        when 'V'  then 5
        when 'IV' then 4
        when 'I'  then 1
      end;
  end loop;

  return l_decimal;
end;
/

select roman_to_decimal('DXV'), roman_to_decimal('MCMLXXXIV')
from dual;

ROMAN_TO_DECIMAL('DXV') ROMAN_TO_DECIMAL('MCMLXXXIV')
----------------------- -----------------------------
                    515                          1984

您可以使用以下方法查看和检查所有转化(1-3 {是to_char()支持的范围)

with t (orig, roman) as (
  select level, to_char(level, 'RN') from dual connect by level < 4000
)
select orig, roman, roman_to_decimal(roman)
from t;

      ORIG ROMAN           ROMAN_TO_DECIMAL(ROMAN)
---------- --------------- -----------------------
         1               I                       1
         2              II                       2
         3             III                       3
         4              IV                       4
         5               V                       5
         6              VI                       6
         7             VII                       7
         8            VIII                       8
         9              IX                       9
        10               X                      10
        11              XI                      11
...
      3994       MMMCMXCIV                    3994
      3995        MMMCMXCV                    3995
      3996       MMMCMXCVI                    3996
      3997      MMMCMXCVII                    3997
      3998     MMMCMXCVIII                    3998
      3999       MMMCMXCIX                    3999

或者只是为了验证它们都转换回了原始值:

with t (original, roman) as (
  select level, to_char(level, 'RN') from dual connect by level < 4000
)
select original, roman, roman_to_decimal(roman)
from t
where roman_to_decimal(roman) != original;

no rows selected

对于我来说,这比递归CTE慢得多,但在其他版本和平台上可能有所不同;就像我说的,只是为了好玩...

答案 1 :(得分:1)

此代码是从LiveSQL站点(https://livesql.oracle.com/apex/livesql/file/content_CESOH7H2D4O88XLW60330Q3L9.html)复制的。作者是Natalka Roshak女士。

  

使用递归子查询将罗马数字转换为十进制格式。将罗马数字放入With子句中的ROMAN内联表中,以传递要转换的罗马数字。

SQL> WITH
  2    roman ( numeral ) AS
  3    ( SELECT 'MCMLXXXVII' AS numeral FROM dual),
  4  romtodec (
  5    thisval,
  6    thisone,
  7    thisdec,
  8    lastdec,
  9    remaining,
 10    pos
 11  ) AS ( SELECT 0 AS thisval,
 12                CAST(NULL AS VARCHAR2(4000) ) AS thisone,
 13                0 AS thisdec,
 14                0 AS lastdec,
 15                roman.numeral AS remaining,
 16                length(roman.numeral) AS pos
 17         FROM roman
 18  UNION ALL
 19  SELECT
 20    CASE
 21      WHEN romtodec.thisdec >= romtodec.lastdec THEN romtodec.thisval + thisdec
 22      ELSE romtodec.thisval - thisdec
 23    END
 24  AS thisval,
 25    substr(romtodec.remaining,length(romtodec.remaining),1) AS thisone,
 26    CASE substr(romtodec.remaining,length(romtodec.remaining),1)
 27      WHEN 'M'   THEN 1000
 28      WHEN 'D'   THEN 500
 29      WHEN 'C'   THEN 100
 30      WHEN 'L'   THEN 50
 31      WHEN 'X'   THEN 10
 32      WHEN 'V'   THEN 5
 33      WHEN 'I'   THEN 1
 34      ELSE 0
 35    END
 36  AS thisdec,
 37    romtodec.thisdec AS lastdec,
 38    substr(romtodec.remaining,1,length(romtodec.remaining) - 1) AS remaining,
 39    length(romtodec.remaining) - 1 AS pos
 40  FROM romtodec
 41  WHERE pos >= 0
 42  ) SELECT thisval
 43    FROM romtodec
 44    WHERE pos IS NULL;

   THISVAL
----------
      1987

SQL>

答案 2 :(得分:0)

请尝试在下面的查询中查询罗马数字到十进制数字:

链接-https://livesql.oracle.com/apex/livesql/file/content_CESOH7H2D4O88XLW60330Q3L9.html

WITH Roman (Numeral) AS
(select 'DXV' as Numeral from dual),
RomToDec (ThisVal, ThisOne, ThisDec, LastDec, Remaining, Pos) AS
(SELECT 0 as ThisVal, cast(null as varchar2(4000)) as ThisOne, 
0 as ThisDec, 0 as LastDec,
Roman.Numeral as Remaining , length(Roman.Numeral) as Pos 
FROM Roman
UNION ALL
SELECT 
case 
  when RomToDec.ThisDec >= RomToDec.LastDec then RomToDec.ThisVal + ThisDec 
  else RomToDec.ThisVal-ThisDec
END as ThisVal, 
substr(RomToDec.Remaining,length(RomToDec.Remaining),1)  as ThisOne, 
case substr(RomToDec.Remaining,length(RomToDec.Remaining),1)  
  when 'M' then 1000
  when 'D' then 500
  when 'C' then 100
  when 'L' then 50
  when 'X' then 10
  when 'V' then 5
  when 'I' then 1
else 0 END as ThisDec,
RomToDec.ThisDec as LastDec,
substr(RomToDec.Remaining,1,length(RomToDec.Remaining)-1) as Remaining,
length(RomToDec.Remaining)-1 as Pos
from RomToDec                           
where Pos >= 0
)
select thisVal
from RomToDec 
where pos is null

答案 3 :(得分:-3)

我认为您正在寻找TO_NUMBER函数。

类似... to_number('XIV', 'RN') ...