可以打印罗马数字而不是int吗?

时间:2013-12-27 02:19:25

标签: c++ locale iostream roman-numerals

我有一些代码打印一些小数字(实际上是几年),请求是将数字打印为Roman numerals而不是使用通常的Hindu-Arabic numerals

int main() {
    // do something to make all integers appear in Roman numerals
    std::cout << "In the year " << 2013 << " the following output was generated:\n";
    // ...
}

如何将int格式化为罗马数字?

1 个答案:

答案 0 :(得分:13)

问题分为两个部分:

  1. 问题的无聊部分是如何将int转换为具有值的罗马表示的字符序列。
  2. 如何拦截int的输出并将其转换为刚刚描述的序列。
  3. 罗马数字遵循相当直接的规则,使用简单的查找表似乎最容易处理。由于问题的主要焦点是如何使其与IOStream一起使用,因此使用了直接的算法:

    template <typename To>
    To make_roman(int value, To to) {
        if (value < 1 || 3999 < value) {
            throw std::range_error("int out of range for a Roman numeral");
        }
        static std::string const digits[4][10] = {
            { "", "M", "MM", "MMM", "", "", "", "", "", "" },
            { "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM" },
            { "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC" },
            { "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX" },
        };
        for (int i(0), factor(1000); i != 4; ++i, factor /= 10) {
            std::string const& s(digits[i][(value / factor) % 10]);
            to = std::copy(s.begin(), s.end(), to);
        }
        return to;
    }
    

    每个&#34;数字&#34;只需查找相应的字符串并将其复制到迭代器即可生成。如果整数超出了可以使用罗马数字表示的值的范围,则抛出异常。可生成的最长字符串长度为15个字符(3888)。

    下一步是设置std::cout,使其使用上述转换格式int。当std::ostream需要转换任何内置数值类型(整数,浮点)或类型boolvoid const*时,它会从std::num_put<cT>方面获取std::locale方面流#{1}}并在对象上调用put(),主要是使用

    std::use_facet<std::num_put<cT>>(s.getloc())
        .put(std::ostreambuf_iterator<char>(s), s, s.fill(), value);
    

    通过从std::num_put<char>派生并覆盖以do_put()为参数的版本的long成员函数,可以更改数字的格式:

    class num_put
        : public std::num_put<char>
    {
        iter_type do_put(iter_type to, std::ios_base& fmt, char fill, long v) const {
            char buffer[16];
            char* end(make_roman(v, buffer));
    
            std::streamsize len(end - buffer);
            std::streamsize width(std::max(fmt.width(0), len));
            std::streamsize fc(width - (end - buffer));
    
            switch (fmt.flags() & std::ios_base::adjustfield) {
            default:
            case std::ios_base::left:
                to = std::copy(buffer, end, to);
                to = std::fill_n(to, fc, fill);
                break;
            case std::ios_base::right:
            case std::ios_base::internal:
                to = std::fill_n(to, fc, fill);
                to = std::copy(buffer, end, to);
            }
            return to;
        }
    };
    

    虽然功能相对较长,但相当简单:

    1. v将转换为罗马数字的字符串并存储在buffer中。
    2. 确定结果字符串的长度和要生成的字符数(并将流width()重置为0)。
    3. 根据输出的对齐位置,复制值后跟存储的填充字符(如果有)或反之。
    4. 剩下的工作是使用此版本的std::locale构面创建std::num_put<char>并将生成的std::locale安装到std::cout中:

      std::cout.imbue(std::locale(std::cout.getloc(), new num_put));
      std::cout << "year " << 2013 << '\n';
      

      Here是一个实例,显示了具有不同路线的几个不同值。 example还实现do_put()的所有四个整数版本(即,longlong longunsigned longunsigned long long