13.4 Форматированный вывод
Библиотека iostream содержит средства, позволяющие выдать данные в той или иной системе счисления с различной точностью, прижатыми к левому или правому концу поля. Средства форматированного вывода позволяют управлять и другими деталями формата выдачи. Все эти нюансы определяются несколькими функциями и флажками, заданными в классе ios битами переменной состояния форматизации следующим образом:
class ios
skipws = 0x0001, // Пропускать пробелы при вводе
left = 0x0002, // Прижать к левому концу поля
right = 0x0004, // Прижать к правому концу поля
internal = 0x0008, // Расположить по центру поля
dec = 0x0010, // Десятичная система счисления
oct = 0x0020, // Восьмеричная система счисления
hex = 0x0040, // Шестнадцатеричная система счисления
showbase = 0x0080, // Показать базу счисления
showpoint = 0x0100, // Отобразить десятичную точку
uppercase = 0x0200, // Печатать шестнадцатеричными буквами
showpos = 0x0400, // Печатать "+" перед положительными числами
scientific = 0x0800, // Для плавающих чисел использовать букву Е;
fixed = 0x1000, // Для плавающих чисел использовать точку 123.45
unitbuf = 0x2000, // Выгрузить все потоки после ввода
stdio = 0x4000 // Выгрузить потоки после вывода
inline long setf(long _f,long _m);
inline long setf(long _l);
inline long unsetf(long _l);
Любой флажок можно установить или сбросить с помощью перегружаемых функций setf() и unsetf(). Например, по умолчанию действует десятичная система счисления. Программист может изменить базу системы счисления на восьмеричную или шестнадцатеричную с помощью функции setf(). Эта функция перегружаемая с одним и двумя аргументами.
Первый аргумент задает конкретный бит, который нужно установить. Он может быть одним из флажков, заданных в перечислении. Чтобы включить сразу несколько флажков, их нужно связать побитовой операцией «или» (|).
Некоторые флажки не могут быть заданы одновременно. Например, нельзя одновременно использовать и восьмеричную, и шестнадцатеричную системы счисления. Если просто задать
то окажутся включенными оба флажка.
То же самое относится к форме выдачи плавающих чисел. Нельзя одновременно использовать и обыкновенную, и научную нотацию. Для таких случаев используется перегружаемая функция setf() с двумя аргументами. Здесь вторым аргументом задается группа флажков, которые необходимо сбросить перед тем, как выполнить установку битов, заданных первым аргументом (табл. 13.1). На месте второго аргумента может появляться одно из двух значений, которые тоже определены в классе ios.
Таблица 13.1 Аргументы функции setf()
Имя второго аргумента
Имя первого аргумента
Такие формы установки (табл. 13.1) гарантируют, что не окажутся одновременно установленными два флажка. Например, чтобы установить шестнадцатеричную систему счисления, нужно написать:
cout.setf(ios::hex, ios::basefield);
Эта функция вначале сбросит в нуль все биты, относящиеся к системе счисления, а затем установит нужный бит.
Точно так же после вызова функции setf():
cout.setf(ios::scientific, ios::floatfield);
числа с плавающей точкой будут печататься в научной нотации. По умолчанию любое значение печатается прижатым к правому концу поля. После установки флажка left значение будет печататься прижатым к левому концу поля. Это можно сделать следующим образом:
cout.setf(ios::left, ios:;adjustfield);
Остальные флажки можно установить функцией setf(), имеющей один аргумент. Функция setf() возвращает предыдущее состояние флажков форматизации в виде числа long. Если это состояние сохранить, то затем его легко можно восстановить. Например:
long old_stform = setf(ios::left, ios::adjustfield;
setf(old_stform, ios::adjustfleld);
По умолчанию выдается столько символов, сколько необходимо для правильного представления значения, т.е.

число не будет дополняться слева или справа пробелами. Поэтому устанавливать флажок левого или правого конца поля при не установленной ширине поля выдачи не имеет смысла, так как ширина поля соответствует числу символов в числе, и значение одновременно прижато и к левому, и к правому концу. Но программист может сам задать ширину выводимого поля с помощью член-функции width(). Функция width () перегружаемая и имеет два прототипа:
int ios::width(int w);
int ios::width();
Первый экземпляр устанавливает ширину печатного поля в переменной w и возвращает предыдущую ширину. Заметим, что если задать в функции width() недостаточное количество позиций, то никакого усечения происходить не будет, а будут печататься все цифры числа. Если ширина поля выдачи задана больше, чем необходимо для числа, то значение слева будет дополнено пробелами. Если при этом включен флажок левого конца поля, то число будет дополняться пробелами справа. Например:
int count1 = 89786, count2 = 7834;
int old_wd = cout.width(l0);
cout.width(old_wd);
cout << count2 << "n";
В результате получим:
В этой программе мы установили ширину поля 10 позиций, сохранив состояние по умолчанию в переменной old_wd. Оператор выдачи cout << напечатает значения переменных прижатыми к правому концу поля и дополнит слева до 10 позиций пробелами. После этого функция width() восстановит состояние по умолчанию, используя для этой цели переменную old_wd, в которой сохранено состояние форматизации по умолчанию.
По умолчанию ширина поля вывода – нуль, а это значит, что будут печататься только значащие цифры и никакого дополнения пробелами ни слева, ни справа не будет. Поэтому значения двух переменных напечатаются слитно, функция width() имеет еще один прототип, который не получает аргументов. Такая функция ничего не изменяет, а просто возвращает текущее значение ширины поля выдачи.
Установленная ширина поля будет сбрасываться в нуль после выполнения любой операции вывода, как, например:
int а, b;
cout << а << b << "n";
В подобном примере установленная в 7 ширина поля будет действительна только в момент выдачи переменной а. Переменные "b" и "n" будут использовать для выдачи ширину поля по умолчанию.
Как упоминалось ранее, если программист установил с помощью функции width() ширину поля выдачи, превышающую необходимый минимум, то значение будет дополняться слева или справа пробелами. Пробел является символом-заполнителем по умолчанию. Но с помощью член-функции fill() программист может изменить символ-заполнитель. Например:
cout << L; // Будет напечатано ####7834
Если использовать флажок left, то вывод будет другим:
cout << L; // Будет напечатано 7834####
При работе с числами с плавающей точкой программист тоже имеет возможность выбора вида выводимого числа. С помощью флажков ios::fixed и ios::scientific можно выбрать нотацию плавающего числа – обыкновенную или научную:
cout.setf(ios::fixed, ios::floatfield);
Выводимое число с плавающей точкой будет печататься в обыкновенной нотации. Кроме этого, программисту предоставлена возможность регулировать точность выводимого числа, т.е. задавать количество цифр после точки. Для этого можно использовать член-функцию precision().
Функция precision(), как и предыдущие функции, перегружаемая и имеет два прототипа:
int precision(int);
int precision();
Первый формат используется, чтобы установить количество печатаемых знаков после десятичной точки. Эта функция возвращает точность, которая использовалась до переустановки. Второй формат функции precision () не изменяет точность, а просто возвращает текущее количество знаков, печатаемых после точки.
По умолчанию печатается шесть значащих цифр после точки. Завершающие нули после десятичной точки усекаются, даже если они заданы явно. Например:
Если после десятичной точки стоят только нули, то усекаются и нули, и десятичная точка. Например:
Если необходимо напечатать все завершающие нули после десятичной точки, необходимо установить флажок ios::showpoint. Например:
Теперь будет выдано
По умолчанию после десятичной точки выдаётся шесть цифр.
Изменим количество выводимых цифр после десятичной точки:
cout << "Сейчас после точки печатается " << cout.precision() << "цифр n";
Этот фрагмент выведет на экран
Сейчас после точки печатается 12 цифр
Вызов cout.precision (12) определил, что после точки нужно печатать 12 знаков. Установка флажка showpoint обеспечила печать двенадцати знаков после точки, даже если это завершающие нули.
Вызов функции precision () без аргументов определил, какова точность в настоящий момент.
Таким образом, комбинируя функции-члены класса ios и флажки, определенные в этом классе, можно организовать форматированный ввод-вывод.
Срочно?
Закажи у профессионала, через форму заявки
8 (800) 100-77-13 с 7.00 до 22.00
Arbitrary Precision — удобная C++ библиотека для работы с длинными целыми числами
Вопреки тому, что авторских C++ библиотек для длинных целых очень много, мне было трудно найти решение, которое было бы простым в использовании на всех этапах (интеграция зависимости, разработка, релиз с зависимостями). Авторские библиотеки имеют одну или несколько проблем реализации: используютв качестве базы счисления, нужна компиляция исходников, не оттестированные, неполный интерфейс (отсутствуют побитовые операторы), не кроссплатформенные. Что касается известных библиотек, то они далеко не просты в использовании:
Boost.Multiprecision — это часть огромного фреймворка. Для больших проектов это отлично, так как в Boost много полезных вещей, но мы далеко не каждый день пишем большие проекты. Кроме того, в Boost длинное целое со знаком хранит несоответствующий размеру диапазон значений, так как у него ещё один бит под знак.
GNU MP — это C библиотека, требует отдельной компиляции и нацелена на Unix-подобные системы. Даже с C++ обёрткой её далеко не просто скомпилировать и использовать.
Crypto++ (https://github.com/weidai11/cryptopp) — самый простой из трёх, но обязательно надо компилировать исходники, с чем не всегда хочется возиться.
Все эти библиотеки отличные для больших и серьёзных проектов. Но есть и простые ситуации: студент первого или второго курса, который хочет сделать более-менее реальный RSA, хотя бы с точки зрения количества бит; программист разрабатывает свой проект, стартап, или даже коммерческий PoC, где не хотелось бы тратить много времени на добавление внешних библиотек, но в то же время не хочется рисковать и брать что-то не совсем надёжное. Авторские библиотеки очень часто протестированы не самым убедительным способом.
Также, в Java, C# и Python длинное целое доступно «из коробки». Всё это подтолкнуло меня к написанию собственной реализации, с надеждой, что в будущем она кому-то будет полезной.
О библиотеке
Arbitrary Precision размещена на GitHub: https://github.com/arbitrary-precision/ap.
В будущем планируется поддерживать длинные числа с плавающей запятой, предлагать более богатый функционал, при этом не изменяя фундаментальному качеству — простота на всех этапах.
Arbitrary Precision как библиотека обладает такими качествами:
Header-only (по умолчанию, с возможностью включить опцию компиляции).
Кроссплатформенность (GCC, MSVC, не должно быть проблем и с другими).
Совместима с разными стандартами (C++11, C++14, C++17).
Не генерирует предупреждений.
Версия 1.2.0, актуальная на момент написания статьи, предлагает типы для знакового и беззнакового длинного целого фиксированного размера со следующим интерфейсом:
Конструкторы копирования и перемещения. Перегружены для свободного преобразования между разными экземплярами класса длинного целого.
Все операторы и конструкторы перегружены для работы с базовыми целыми типами.
Конструктор, методы и операторы для инициализации из std::string , const char* . Преобразование в std::string с поддержкой базы до 16.
Все бинарные арифметические и побитовые операторы (кроме сдвига), их варианты с присваиванием и операторы сравнения перегружены для всех базовых целых типов и для всех экземпляров класса длинного целого.
Операторы сдвига в качестве правого операнда принимают только базовые целые типы (пока что).
Все унарные операторы (
Операторы ввода и вывода с распознаванием флагов базы для вывода.
Использование
Добавление библиотеки в проект элементарное. Нужно скачать исходный код и подключить заголовок ap.hpp (где бы он не находился, обычно это будет <ap/ap.hpp> ). В глобальном пространстве имён доступно два шаблона: знаковый ap_int<size_t> и беззнаковый ap_uint<size_t> , которые в качестве параметра принимают размер типа в битах.
Нет смысла детально расписывать и демонстрировать использование операторов сравнения, арифметических и побитовых операторов во всевозможных ситуациях. Эти типы ведут себя точно так же, как и обычный int и unsigned int . Краткий пример:
Список важных моментов, которые следует помнить при использовании:
Знаковые числа ведут себя так, будто они представлены в дополнительном коде (изнутри это не так). Диапазон значений соответствует размеру, указанному в качестве параметра шаблона.
Деление на ноль задействует обычный сценарий (просто делит на ноль).
Сдвиги арифметические. Если сдвиг происходит на количество битов, большее, чем размер типа, то результат 0. Если это была операция сдвига вправо и число было отрицательным, то результат -1.
Размер типа в битах должен быть строго больше unsigned long long .
Размер типа в битах должен быть кратен размеру AP_WORD — типа, который используется для хранения данных ( unsigned int по умолчанию).
Более интересным является взаимодействие со строками. Основным методом класса для преобразования из строки является set :
Этот метод вызывается другими вариантами:
Чтобы преобразовать в строку, есть два способа:
Вне зависимости от базы, преобразованная строка имеет представление «знак и модуль».
Если при преобразовании в строку указана база 2, 8 или 16, то всегда добавляется префикс.
Также перегружены операторы ввода-вывода. Тут ничего особенного. Ввод — чтение строки и преобразование из нее. Вывод — преобразование в строку, если установлены флаги std::ios_base::oct или std::ios_base::hex то будет использована соответствующая база.
Режим компиляции исходников
Если определить макрос AP_USE_SOURCES , то .cpp файлы должны быть скомпилированы отдельно. Это опция для тех, кто предпочитает компиляцию, а не header-only подход. Если макрос AP_USE_SOURCES не определён, а .cpp всё равно компилируют, то эту ситуацию обработает compile guard (на примере asm.*):
Быстродействие
Данные замерялись простым способом. Для указанного размера целого числа генерируется левый операнд, где значение занимает 25-50% размера и правый операнд, где значение занимает 12-25% размера. Дальше выполняется операция, и замеряется время исполнения для AP и Boost. В таблице указано соотношение AP/Boost.
Версия компилятора GCC: Ubuntu 7.5.0-3ubuntu1
AP несущественно проигрывает Boost на больших значениях (1.1-1.5 раза), деление же намного лучше. Малые значения пока что не оптимизированы, из-за этого такая заметная разница. (2-6 раз).
Примечание: для замера использовалась программа https://github.com/arbitrary-precision/ws/blob/master/src/measure/measure.cpp. Из-за того, что Boost использует __int128 при компиляции с GCC, пришлось сконфигурировать AP, чтобы она тоже использовала этот тип (корневой CMakeLists.txt):
__int128 не полностью поддерживается сейчас — он ломает взаимодействие с базовыми типами. На операции с длинными числами это не влияет, они работают корректно.
Внутреннее устройство
Вся библиотека базируется на трёх типах:
word_t , или слово. Аналог limb-ов в GNU MP и Boost, служит для хранения данных. Этот тип можно задать через макрос AP_WORD .
dword_t , или двойное слово. Аналог limb в GNU MP и Boost, служит для выполнения операций и хранения промежуточных значений. Этот тип можно задать через макрос AP_DWORD , размер должен быть ровно в два раза больше, чем размер word_t .
index_t , аналог size_t .
Можно выделить пять основных сущностей библиотеки:
Шаблон integer<size_t Size, bool Sign> (как контейнер значения, integer.hpp) — его частичными специализациями являются ap_int<size_t> и ap_uint<size_t> .
dregister<T> (core.hpp), или «регистр» — POD тип, единое легковесное представление длинного целого. Хранит указатель на массив слов ( word_t ), T и есть типом указателя — const word_t* или word_t* в зависимости от того, разрешена ли запись значения. Остальные поля — размер массива, размер значения (сколько слов задействовано в данный момент), и знак.
Внешние функции (integer.hpp) — все методы integer<size_t, bool> и все внешние операторы, перегруженные для него. Работают на только на уровне класса integer<size_t, bool> . Ответственность — обеспечение взаимодействия всевозможных типов.
Внутренние функции (integer_api.*) — набор функций, которые использует integer в своих методах. Работают на уровне dregister<T> . Разделены на две группы — для знаковых и беззнаковых, не накладывают никаких ограничений на операнды. Ответственность — подготовка операндов для вызова алгоритмических функций, корректная расстановка знаков, нормализация после выполнения алгоритма (о ней чуть ниже).
Алгоритмические функции (asm.*) — являются чистым алгоритмом, работают с dregister<T>, но интерпретируют его исключительно как беззнаковое целое. Кроме того, они накладывают строгие ограничения на операнды и могут иметь очень специфическое поведение. Арифметические алгоритмы — это алгоритмы A, S, M, D из «Искусство программирования (Том 2)» Д. Кнута. Остальные написаны самостоятельно. Ответственность — выполнение операции, получение корректного с точки зрения беззнакового числа битового паттерна.
Нормализация, упомянутая выше, это процесс приведения знакового и беззнакового числа к нормальной форме:
Размер должен быть установлен таким образом, что word_t с индексом [размер — 1] не равен нулю.
Знак при размере 0 должен быть 0.
(только знаковое) Арифметическое значение, представляемое dregister<T> числа не должно выходить за рамки диапазона , где n — битовый размер числа.
С первыми двумя пунктами всё вполне прозрачно: «-0» это недопустимое значение, а нули в начале старших разрядов ломают алгоритм деления и оперировать нулями не имеет смысла. Третий пункт обеспечивает то, что знаковое ведёт себя так, будто оно представлено в дополнительном коде. После выполнения операции проверяется старший бит массива слов. Если он 1, то весь массив перекидывается в его дополнительный код и числу выставляется знак «-«. Это работает даже если ненулевым является исключительно старший бит (дополнительный код такого паттерна это он сам). После этого выполняется нормализация по первым двум пунктам.
Тестирование
Проблемой является то, как покрыть всевозможные сценарии взаимодействия, при этом учитывая исключительные случаи. В то же время, тестирование должно быть прозрачным, отдельная проверка должна использовать одни и те же входные значения каждый раз.
В качестве эталонной библиотеки выбрана Boost.Multiprecision. Единое строковое представление — «знак и модуль» в шестнадцатеричной системе. Целое число в тестировании характеризуется следующими параметрами:
Битовый размер. Большой (L, 512 бит) или малый (S, 256 бит).
Размер (данных). Одно слово, 25%, 50%, 75%. 100% от битового размера (это всегда целое количество слов).
Знаковость. Знаковое или беззнаковое.
Битовый паттерн. Определено 10:
00000000 00000000 . 00000000 00000000
11111111 11111111 . 11111111 11111111
Малый шахматный 0
01010101 01010101 . 01010101 01010101
Малый шахматный 1
10101010 10101010 . 10101010 10101010
Большой шахматный 0
00000000 11111111 . 00000000 11111111
Большой шахматный 1
11111111 00000000 . 11111111 00000000
00000000 00000000 . 00000000 00000001
10000000 00000000 . 00000000 00000000
Все, кроме младшего
11111111 11111111 . 11111111 11111110
Все, кроме старшего
01111111 11111111 . 11111111 11111111
Всего отдельных комбинаций для абстрактной бинарной операции 80000:
8 комбинаций битовых размеров (левый операнд, правый операнд, результат) — LLL, LLS, LSL, LSS, SLL, SLS, SSL, SSS. Считается, что все три аргумента передаются независимо друг от друга.
25 комбинаций размеров (5 левый, 5 правый).
4 комбинации знаковости операндов (в отличие от битового размера, зависит от операндов).
100 комбинаций битовых паттернов (10 левый, 10 правый).
Эти тесты запускаются и для внешних, и для внутренних функций отдельно. Некоторые сценарии не релевантны, они отсеяны. Внешние функции никогда не могут иметь сценарий битовых размеров LLS или SSL (int + int != long long int), а внутренние не имеют функций, которые бы работали с операндами разной знаковости. Т.е. количество тестов 60000 и 40000 соответственно.
Отдельная проверка выглядит следующим образом:
Сгенерировать операнды заданного размера относительно заданного битового размера.
Инициализировать целые Boost, результат конвертировать в беззнаковою строку (т.е дополнительный код). Эту строку подогнать под заданные размер и знаковость третьего аргумента (хранящего результат).
Инициализировать операнды ap (для внешних функций это integer<size_t, bool> , для внутренних — dregister<T> . Затем выполнить операцию и просто конвертировать результат в строку, не выполняя никаких других превращений.
Сравнить полученные строки, они должны быть идентичны.
Реализовано это в репозитории ws, который был н енапрямую упомянут в «Быстродействие»: https://github.com/arbitrary-precision/ws (подпроект src/validate). Детали реализации в этой статье приводиться не будут, это довольно большой объём информации.
Что в планах
Написать тесты для взаимодействия с базовыми типами.
Разобраться с C++20 и SpaceX оператором.
Оптимизация унарных операторов.
Написать специализацию integer<size_t, bool> для битового размера равному unsigned long long и меньше.
Учесть существование __int128 и возможность существования ещё больших intX .
Написание исчерпывающей документации как для использования, так и для разработки.
Оптимизация работы с базовым типом.
Улучшить взаимодействие между signed и unsigned (избегать приведения типа).
Расширить интерфейс дополнительными базовыми алгоритмами (sqrt, lcm, gcd, pow etc.).
Почему cout.precision () влияет на весь поток?
Я чувствую, что задаю очень простой вопрос, но я не смог найти ответ здесь или в Google. Я помню, что нас этому учили в школе, но, увы, за многие годы это исчезло.
Почему cout.precision() ( std::ios_base::precision() ) влияет на весь поток при вызове в середине списка вывода? Я знаю правило, что std::setprecision() следует использовать для изменения точности на лету, и это cout.precision() собирается испортить вывод значением, которое он возвращает. Но какова механика этого? Это из-за буферизации? В руководствах говорится, что они «делают то же самое», но опытным путем я вижу, что это не совсем так.
Является ли это «реализацией конкретной / не определенной стандартом»?
Не стесняйтесь пометить это как дубликат, потому что я не смог найти его на SO.
Решение
Порядок оценки аргументов функции не указан. Когда вы звоните std::cout.precision(n) точность std::cout ‘устанавливается в момент, когда этот вызов оценивается. В вашем выражении
cout.precision(3) это, по-видимому, называется первым, что полностью разрешено компилятору. Помните, что компилятор считает приведенное выше выражение эквивалентным
Практически кажется, что аргументы вашей функции компилятора вычисляются справа налево. Только тогда выполняются различные операторы сдвига. В результате точность изменяется до выполнения вывода.
Использование манипуляторов, таких как std::setprecision(n) избегает полагаться на порядок, подвыражения оцениваются: временный созданный из std::setprecision(n) создается всякий раз, когда это. Затем эффект применяется, когда вызывается соответствующий оператор сдвига. Поскольку операторы сдвига должны вызываться в соответствующем порядке, использование манипулятора происходит в известном месте.
Другие решения
Точное время, когда cout.precision(3) оценивается в вашем первом примере не определено, потому что его значение служит аргументом для вызова функции. OTOH со вторым примером, время, когда setprecision(3) вставлен в поток очень хорошо определен — т.е. после endl вставляется и перед test (2-й раз). Поэтому первая версия не даст надежных результатов (то, что вы получите, может отличаться в разных реализациях), но вторая версия даст.
Увидеть этот вопрос для более подробного объяснения. (Вопрос там не использует операторы, но принцип тот же — вызов функции-члена для возвращаемого значения другой функции.)