Как напечатать __int128 в g++?
Я использую встроенный тип GCC __int128
для некоторых вещей в моей программе на C++ ничего существенного, по крайней мере, недостаточно, чтобы оправдать использование библиотеки BigInt только для этого, и, тем не менее, достаточно, чтобы предотвратить его полное удаление.
Моя проблема возникает, когда я сталкиваюсь с печатными частями своих классов, вот минимальный пример:
#include <iostream>
int main()
{
__int128 t = 1234567890;
std::cout << t << std::endl;
return t;
}
Комментируя std::cout
строка сделает этот код хорошо скомпилированным g++
, но его появление приведет к следующему сообщению об ошибке:
int128.c: In function ‘int main()’:
int128.c:7:13: error: ambiguous overload for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream<char>}’ and ‘__int128’)
std::cout << t << std::endl;
^
int128.c:7:13: note: candidates are:
In file included from /usr/include/c++/4.9/iostream:39:0,
from int128.c:1:
/usr/include/c++/4.9/ostream:108:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__ostream_type& (*)(std::basic_ostream<_CharT, _Traits>::__ostream_type&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>] <near match>
operator<<(__ostream_type& (*__pf)(__ostream_type&))
^
/usr/include/c++/4.9/ostream:108:7: note: no known conversion for argument 1 from ‘__int128’ to ‘std::basic_ostream<char>::__ostream_type& (*)(std::basic_ostream<char>::__ostream_type&) {aka std::basic_ostream<char>& (*)(std::basic_ostream<char>&)}’
/usr/include/c++/4.9/ostream:117:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__ios_type& (*)(std::basic_ostream<_CharT, _Traits>::__ios_type&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>; std::basic_ostream<_CharT, _Traits>::__ios_type = std::basic_ios<char>] <near match>
operator<<(__ios_type& (*__pf)(__ios_type&))
^
/usr/include/c++/4.9/ostream:117:7: note: no known conversion for argument 1 from ‘__int128’ to ‘std::basic_ostream<char>::__ios_type& (*)(std::basic_ostream<char>::__ios_type&) {aka std::basic_ios<char>& (*)(std::basic_ios<char>&)}’
/usr/include/c++/4.9/ostream:127:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(std::ios_base& (*)(std::ios_base&)) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>] <near match>
operator<<(ios_base& (*__pf) (ios_base&))
^
/usr/include/c++/4.9/ostream:127:7: note: no known conversion for argument 1 from ‘__int128’ to ‘std::ios_base& (*)(std::ios_base&)’
/usr/include/c++/4.9/ostream:166:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
operator<<(long __n)
^
/usr/include/c++/4.9/ostream:170:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
operator<<(unsigned long __n)
^
/usr/include/c++/4.9/ostream:174:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(bool) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
operator<<(bool __n)
^
In file included from /usr/include/c++/4.9/ostream:609:0,
from /usr/include/c++/4.9/iostream:39,
from int128.c:1:
/usr/include/c++/4.9/bits/ostream.tcc:91:5: note: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(short int) [with _CharT = char; _Traits = std::char_traits<char>]
basic_ostream<_CharT, _Traits>::
^
In file included from /usr/include/c++/4.9/iostream:39:0,
from int128.c:1:
/usr/include/c++/4.9/ostream:181:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(short unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
operator<<(unsigned short __n)
^
In file included from /usr/include/c++/4.9/ostream:609:0,
from /usr/include/c++/4.9/iostream:39,
from int128.c:1:
/usr/include/c++/4.9/bits/ostream.tcc:105:5: note: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(int) [with _CharT = char; _Traits = std::char_traits<char>]
basic_ostream<_CharT, _Traits>::
^
In file included from /usr/include/c++/4.9/iostream:39:0,
from int128.c:1:
/usr/include/c++/4.9/ostream:192:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
operator<<(unsigned int __n)
^
/usr/include/c++/4.9/ostream:201:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long long int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
operator<<(long long __n)
^
/usr/include/c++/4.9/ostream:205:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long long unsigned int) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
operator<<(unsigned long long __n)
^
/usr/include/c++/4.9/ostream:220:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(double) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
operator<<(double __f)
^
/usr/include/c++/4.9/ostream:224:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(float) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
operator<<(float __f)
^
/usr/include/c++/4.9/ostream:232:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(long double) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>]
operator<<(long double __f)
^
/usr/include/c++/4.9/ostream:245:7: note: std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(const void*) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__ostream_type = std::basic_ostream<char>] <near match>
operator<<(const void* __p)
^
/usr/include/c++/4.9/ostream:245:7: note: no known conversion for argument 1 from ‘__int128’ to ‘const void*’
In file included from /usr/include/c++/4.9/ostream:609:0,
from /usr/include/c++/4.9/iostream:39,
from int128.c:1:
/usr/include/c++/4.9/bits/ostream.tcc:119:5: note: std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT, _Traits>::operator<<(std::basic_ostream<_CharT, _Traits>::__streambuf_type*) [with _CharT = char; _Traits = std::char_traits<char>; std::basic_ostream<_CharT, _Traits>::__streambuf_type = std::basic_streambuf<char>] <near match>
basic_ostream<_CharT, _Traits>::
^
/usr/include/c++/4.9/bits/ostream.tcc:119:5: note: no known conversion for argument 1 from ‘__int128’ to ‘std::basic_ostream<char>::__streambuf_type* {aka std::basic_streambuf<char>*}’
In file included from /usr/include/c++/4.9/iostream:39:0,
from int128.c:1:
/usr/include/c++/4.9/ostream:493:5: note: std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, unsigned char) [with _Traits = std::char_traits<char>]
operator<<(basic_ostream<char, _Traits>& __out, unsigned char __c)
^
/usr/include/c++/4.9/ostream:488:5: note: std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, signed char) [with _Traits = std::char_traits<char>]
operator<<(basic_ostream<char, _Traits>& __out, signed char __c)
^
/usr/include/c++/4.9/ostream:482:5: note: std::basic_ostream<char, _Traits>& std::operator<<(std::basic_ostream<char, _Traits>&, char) [with _Traits = std::char_traits<char>]
operator<<(basic_ostream<char, _Traits>& __out, char __c)
^
/usr/include/c++/4.9/ostream:476:5: note: std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&, char) [with _CharT = char; _Traits = std::char_traits<char>]
operator<<(basic_ostream<_CharT, _Traits>& __out, char __c)
^
Да, я знаю, много строк, чтобы объяснить это __int128
просто неправильно обрабатывается...
Есть ли простой способ получить __int128
быть напечатанным iostream
как любые другие числовые типы?
РЕДАКТИРОВАТЬ: Для тех, кто все еще путает C и C++, да, я прочитал вопрос: как напечатать число __uint128_t с помощью gcc? Но это было для C, а не для C++, как я сейчас спрашиваю.
9 ответов
Если вам не нужны какие-либо необычные параметры форматирования, напишите <<
Оператор тривиален. Формально, я подозреваю, что написание одного для __int128_t
будет рассматриваться как неопределенное поведение, но практически, я думаю, это будет работать, пока библиотека не начнет оказывать ему реальную поддержку (в этот момент вы перестанете работать с вашим оператором преобразования).
В любом случае, что-то вроде следующего должно работать:
std::ostream&
operator<<( std::ostream& dest, __int128_t value )
{
std::ostream::sentry s( dest );
if ( s ) {
__uint128_t tmp = value < 0 ? -value : value;
char buffer[ 128 ];
char* d = std::end( buffer );
do
{
-- d;
*d = "0123456789"[ tmp % 10 ];
tmp /= 10;
} while ( tmp != 0 );
if ( value < 0 ) {
-- d;
*d = '-';
}
int len = std::end( buffer ) - d;
if ( dest.rdbuf()->sputn( d, len ) != len ) {
dest.setstate( std::ios_base::badbit );
}
}
return dest;
}
Обратите внимание, что это просто быстрое временное исправление до тех пор, пока библиотека g++ не поддержит тип. Он рассчитывает на 2 дополнения, обернуть на переполнение, для __int128_t
, но я был бы очень удивлен, если бы это было не так (формально, это неопределенное поведение). Если нет, вам нужно исправить инициализациюtmp
, И, конечно же, он не обрабатывает какие-либо параметры форматирования; Вы можете добавить по желанию. (Обработка набивки иadjustfield
правильно может быть нетривиальным.)
Я бы порекомендовал против перегрузки operator<<
за __int128_t
, Причина в том, что всякий раз, когда вы видите cout << x
для некоторого целочисленного типа можно ожидать, что все виды манипуляторов, такие как std::hex
или же std::setw
также должен работать. Самое важное правило при перегрузке операторов: "делай, как делают целые".
В качестве альтернативы я бы рекомендовал использовать decimal_string(__int128_t)
функция, которую вы можете использовать как cout << decimal_string(x);
в вашем коде. Для преобразования строк вы можете использовать алгоритм из любых вопросов и ответов, связанных с Си. Это дает понять, что у вас есть специальный код для ваших 128-битных целых. Всякий раз, когда стандартная библиотека обновляется до 128-битной поддержки, вы можете отказаться от нее (и это легко grep
для этих функций).
Обманчиво простой подход
std::ostream& operator<<(std::ostream& os, __int128 x){
if(x<0) return os << "-" << -x;
if(x<10) return os << (char)(x+'0');
return os << x/10 << (char)(x%10+'0');
}
Смотрите мой другой комментарий о неудачной попытке более производительной реализации.
Акции cout
не обрабатывает __int128
, но вы можете расширить его с помощью своей собственной функции.
Для начала, напишите что-то вроде этого:
std::ostream& operator<<(std::ostream& os, __int128 t) {
// TODO: Convert t to string
return os << str;
}
Есть много решений для преобразования 128-битного числа в строку, я не буду повторяться здесь.
О совместимости библиотеки в комментариях:
Вам нужно только свернуть свою собственную функцию, если стандартная библиотека не предоставляет такой обработчик. Как только библиотека поддерживает тип, вы должны увидеть конфликт при сборке, что-то вроде [note: встроенный оператор-кандидат<<], попробуйте это с int64_t.
Отмечая предупреждение о перегрузке<<
может ввести в заблуждение из-за того, что, возможно, не обеспечивается ожидаемая поддержка манипуляций, вот версия, которая поддерживает манипуляции:
#include <iostream>
#include <iomanip>
// Write the 128-bit integer val to out, with a minus sign if decimal and neg
// is true. Obey all of the ostream settings of out for integer display: octal
// or hexadecimal, upper case letters, plus sign, fill character and width, and
// fill placement.
static void out128(std::ostream& out, __uint128_t val, int neg) {
// Note if the number is zero. (No hex or octal prefix in this case.)
auto zero = val == 0;
// Note if upper-case letters requested.
auto state = out.flags();
auto upper = (state & std::ios_base::uppercase) != 0;
// Set base for digits.
unsigned base = state & std::ios_base::hex ? 16 :
state & std::ios_base::oct ? 8 :
10;
// Space for digits and prefix. Generate digits starting at the end of the
// string, going backwards. num will be the digit string. Terminate it.
char str[47];
auto end = str + sizeof(str), num = end;
*--num = 0;
// Compute and place digits in base base.
do {
char dig = val % base;
val /= base;
dig += dig < 10 ? '0' : (upper ? 'A' : 'a') - 10;
*--num = dig;
} while (val);
// Prepend octal number with a zero if requested.
if (state & std::ios_base::showbase && base == 8 && !zero)
*--num = '0';
// pre will be the prefix string. Terminate it.
auto pre = num;
*--pre = 0;
// Put a plus or minus sign in the prefix as appropriate.
if (base == 10) {
if (neg)
*--pre = '-';
else if (state & std::ios_base::showpos)
*--pre = '+';
}
// Prefix a hexadecimal number if requested.
else if (state & std::ios_base::showbase && base == 16 && !zero) {
*--pre = upper ? 'X' : 'x';
*--pre = '0';
}
// Compute the number of pad characters and get the fill character.
auto len = (num - pre) + (end - num) - 2;
auto pad = out.width();
out.width(0);
pad = pad > len ? pad - len : 0;
char fill = out.fill();
// Put the padding before prefix if neither left nor internal requested.
if (!(state & (std::ios_base::internal | std::ios_base::left)))
while (pad) {
out << fill;
pad--;
}
// Write prefix.
out << pre;
// Put the padding between the prefix and the digits if requested.
if (state & std::ios_base::internal)
while (pad) {
out << fill;
pad--;
}
// Write digits.
out << num;
// Put number to the left of padding, if requested.
if (state & std::ios_base::left)
while (pad) {
out << fill;
pad--;
}
}
// Overload << for an unsigned 128-bit integer.
std::ostream& operator<<(std::ostream& out, __uint128_t val) {
out128(out, val, 0);
return out;
}
// Overload << for a signed 128-bit integer. Negation of the most negative
// signed value gives the correct unsigned absolute value.
std::ostream& operator<<(std::ostream& out, __int128_t val) {
auto state = out.flags();
if (val < 0 && !(state & (std::ios_base::hex | std::ios_base::oct)))
out128(out, (__uint128_t)-val, 1);
else
out128(out, (__uint128_t)val, 0);
return out;
}
Если это не критично для производительности, вот простой, читаемый способ преобразования int128 в строку base-10 (которую затем можно распечатать, конечно):
std::string toString(__int128 num) {
std::string str;
do {
int digit = num % 10;
str = std::to_string(digit) + str;
num = (num - digit) / 10;
} while (num != 0);
return str;
}
Мы можем сделать это в несколько раз быстрее, получая цифры большими блоками вместо одного за раз. Но это требует, чтобы мы проверяли каждый блок на наличие лидирующих нулей, которые были потеряны, и добавляли их обратно:
std::string toString(__int128 num) {
auto tenPow18 = 1000000000000000000;
std::string str;
do {
long long digits = num % tenPow18;
auto digitsStr = std::to_string(digits);
auto leading0s = (digits != num) ? std::string(18 - digitsStr.length(), '0') : "";
str = leading0s + digitsStr + str;
num = (num - digits) / tenPow18;
} while (num != 0);
return str;
}
Примечание: я также разместил версию этого ответа для неподписанных int128s здесь.
Самая эффективная версия, которую я мог придумать
std::ostream& operator<<(std::ostream& os, __int128 x){
if(x<0){ os << "-"; x = -x; }
uint64_t tenPow18 = 1000000000000000000;
uint64_t x1 = x%tenPow18;
x/=tenPow18;
if(x>0){
uint64_t x2 = x%tenPow18;
x/=tenPow18;
if(x>0) os << (short) x;
os << x2;
}
return os << x1;
}
просто печатает два 64-битных и одно 16-битное целое число. Это сводится к развернутой и упрощенной версии ответа @Gumby The Green и @HackerBoss.
Приведенный выше код неверен, так как он не учитывает начальные нули для промежуточных отпечатков. Например, "1000000000000000001" будет напечатано как "11". Ответы @Gumby The Green и @HackerBoss, вероятно, тоже страдают от этого.
В конце концов, я дал ответ в своем другом комментарии.
Ответы пока хорошие, но я просто хотел добавить к ответу Джеймса Канце. Во-первых, обратите внимание, что из-за беззнакового преобразования он не будет работать для числа-0x80000000000000000000000000000000
. Во-вторых, вы можете воспользоваться тем фактом, что печать с 64-битными целыми числами работает, чтобы оптимизировать реализацию функции следующим образом:
std::ostream& operator<<(std::ostream& os, __int128_t value) {
if (value < 0) {
os << '-';
value = -value;
}
// save flags to restore them
std::ios_base::fmtflags flags(os.flags());
// set zero fill
os << std::setfill('0') << std::setw(13);
// 128-bit number has at most 39 digits,
// so the below loop will run at most 3 times
const int64_t modulus = 10000000000000; // 10**13
do {
int64_t val = value % modulus;
value /= modulus;
if (value == 0) {
os.flags(flags);
return os << val;
}
os << val;
} while (1);
}
Хотя это такой старый вопрос, это лучший результат, когда я искал способ печати __int128 на экран с помощью C++. Недавно мне пришлось это сделать, и я нашел ответ Джеймса Канзе, который лучше всего подходит для моего заявления и является наиболее простым для понимания. Это просто урезанная и упрощенная версия:
using Integer128 = __int128;
std::ostream& operator<<(std::ostream& dest, const Integer128& value)
{
Integer128 tmp = value < 0 ? -value : value;
std::array<char, 128> buffer;
auto d = buffer.begin();
while(tmp != 0){
*d++ = "0123456789"[tmp % 10];
tmp /= 10;
}
if(value < 0){
*d++ = '-';
}
std::ostream_iterator<char> out_it(dest);
std::reverse_copy(buffer.begin(), d, out_it);
return dest;
}