Можно ли печатать римские цифры вместо int?

У меня есть некоторый код, который печатает несколько небольших чисел (на самом деле, лет), и просьба, чтобы номера печатались как римские цифры вместо использования обычного Индуистско-арабские цифры:

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как римские цифры?

4

Решение

Вопрос состоит из двух частей:

  1. Скучная часть вопроса — как трансформировать int в последовательность символов с римским представлением значения.
  2. Как перехватить вывод int и включите его в только что описанную последовательность.

Римские цифры следуют довольно прямолинейному правилу, которое, кажется, обрабатывается проще всего с помощью простой справочной таблицы. Поскольку основное внимание в этом вопросе уделяется тому, как заставить его работать с IOStreams, используется прямой алгоритм:

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

Каждая «цифра» просто получается путем поиска соответствующей строки и копирования ее в итератор. Если целое число выходит за пределы диапазона для значения, которое может быть представлено римскими цифрами, генерируется исключение. Самая длинная строка из 15 символов (3888).

Следующим шагом является настройка std::cout так, что он форматирует intс использованием вышеуказанного преобразования. Когда std::ostream необходимо преобразовать любой из встроенных числовых типов (целые числа, числа с плавающей запятой) или типы bool а также void const*, он получает std::num_put<cT> аспект от потока std::locale и звонки 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. В зависимости от того, где выровнены выходные данные, либо копируется значение, за которым следуют заполняющие символы (если они есть), либо наоборот.

Осталось создать 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';

Вот это живой пример, показывающий пару разных значений с разными выравниваниями. пример также реализует все четыре целочисленных версии do_put() (т.е. для long, long long, unsigned long, а также unsigned long long).

13

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]