Warm tip: This article is reproduced from stackoverflow.com, please click
c c++

Roman Numerals to Arabic (vinculum)

发布于 2020-03-29 21:02:27

I am currently working on a project that converts Roman Numerals to Arabic Numbers and vice versa. I am also responsible to implement concepts like vinculum, where if you put a bar on top of a Roman numeral, the numbers below will be multiplied by 1,000.

The problem I am having is I can get only one side working, meaning: I can either just convert from Roman Numeral to Arabic without Vinculum: ex. I = 1, II = 2 However, when this works my vinculum code does not work.

Here is a snippet of my code:

int romanToDecimal(char input[], size_t end) {

int roman = 0;

int vroman = 0;
for (int i = 0; i < strlen(input); ++i)
{
    int s1 = value(input[i]);
    int s2 = value(input[i]);

   if (input[i] == '-')
    {

        for (int j = i - 1; j >= 0; --j)
        {

            roman = (roman + value(input[j]));

        }

        roman *= 1000;



        for (int k = i + 1; k <= strlen(input); k++)
            roman += value(input[k]);

    }
    else
        roman += s1;
}

    return roman;

}

We use '-' instead of the bar on top of the characters, because we cannot do that in computer easily. So IV-, would be 4000 and XI- would be 11,000 etc...

I understand that the way I am doing the loop is causing some numbers that were converted to add twice, because if(input[i] == '-') cycles through each character in the string one at a time.

OK, so my question is basically what is the logic to get it to work? So if the string contains '-' it will multiply the number by 1000, if the string does not contain '-' at ALL, then it will just convert as normal. Right now I believe what is happening is that when "if (input[i] == '-')" is false, that part of the code still runs, how do I not get it to run at all when the string contains '-'??

Questioner
J J
Viewed
24
Bob__ 2020-01-31 18:47

The posted code seems incomplete or at least has some unused (like end, which if it represents the length of string could be used in place of the following repeated strlen(input)) or meaningless (like s2) variables.

I can't understand the logic behind your "Vinculum" implementation, but the simple

roman += s1;  // Where s1 = value(input[i]);

It's clearly not enough to parse a roman number, where the relative position of each symbol is important. Consider e.g. "IV", which is 4 (= 5 - 1), vs. "VI", which is 6 (= 5 + 1).

To parse the "subtractive" notation, you could store a partial result and compare the current digit to the previous one. Something like the following:

#include <stdio.h>
#include <string.h>

int value_of(char ch);

long decimal_from_roman(char const *str, size_t length)
{
    long number = 0, partial = 0;
    int value = 0, last_value = 0;
    for (size_t i = 0; i < length; ++i)
    {
        if (str[i] == '-')
        {
            number += partial;
            number *= 1000;
            partial = 0;
            continue;
        }
        last_value = value;
        value = value_of(str[i]);
        if (value == 0)
        {
            fprintf(stderr, "Wrong format.\n");   
            return 0;
        }
        if (value > last_value)
        {
            partial = value - partial;
        }
        else if (value < last_value)
        {
            number += partial;
            partial = value;
        }
        else
        {
            partial += value;   
        }
    }   
    return number + partial;
}

int main(void)
{
    char const *tests[] = {
        "I", "L", "XXX", "VI", "IV", "XIV", "XXIII-",
        "MCM", "MCMXII", "CCXLVI", "DCCLXXXIX", "MMCDXXI", // 1900, 1912, 246, 789, 2421
        "CLX", "CCVII", "MIX", "MLXVI"                     // 160, 207, 1009, 1066 
    };
    int n_samples = sizeof(tests) / sizeof(*tests);

    for (int i = 0; i < n_samples; ++i)
    {
        long number = decimal_from_roman(tests[i], strlen(tests[i]));
        printf("%12ld %s\n", number, tests[i]);
    }

    return 0;
}

int value_of(char ch)
{
    switch (ch)
    {
        case 'I':
            return 1;
        case 'V':
            return 5;
        case 'X':
            return 10;
        case 'L':
            return 50;
        case 'C':
            return 100;
        case 'D':
            return 500;
        case 'M':
            return 1000;
        default:
            return 0;
    }
}

Note that the previous code only checks for wrong characters, but doesn't discard strings like "MMMMMMMMMMIIIIIIIIIIIIIV". Consider it just a starting point and feel free to improve it.