I had a draft for int128 handling reviewed there:
int128 handling in c-code, gcc / glibc / linux
I changed a lot according to the hints there, while leaving in, e.g.,
a trailing space on each line, the disclaimer and some 'internal notes'.
My intention is as described in the other thread and in the code
header and comments.
/*
* Copyright 2025 ... B. Samtleben,
* Based on others work, see below, alas in chaotic trial and error not all
* sources noted, will try to re-find origins for crediting.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Check to match / fullfill evtl. restrictions / copyrights of the origins.
* 2. As well for using / distributing source code as binaries.
* 3. Forward the following disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
// Issue, goal: GNU gcc / glibc programming workbench provides 128-bit integers
// with different names ( __int128, int_128? int_128_t? ), already confusing.
// It doesn't provide read routine, print routine or print format specifier.
// Working with such makes me feel blind, requires guessing, and likely
// introduces errors.
// The web is flooded with naive questions and - IMHO - insuffucient solutions
// about the topic. The routines / macros provided here try to emulate:
// - constants:
// INT_MAX, INT_MIN LONG_MAX ... by U128_MIN, U128_MAX, I128_MIN, I128_MAX,
// add. U128_MAX_DIV10, U128_MAX_MOD10, I128_MAX_DIV10 and I128_MAX_MOD10
// are added to handle overflow detection.
// - input:
// strtol, strtoul, atoi by strtou128, strtoi128,
// - output:
// u128tostr( str, x ) and i128tostr( str, x ) are intended to provide
// snprintf functionality, but without length limiter,
// printf functionality by printf( " %s ", i128tostr( x ) ) a little less
// complicated than quadmath_snprintf.
// ressources:
// "6.9 128-bit Integers" - int128 support in gcc / glibc,
// https://gcc.gnu.org/onlinedocs/gcc/_005f_005fint128.html
// "Codeforces / DrinkMoreBoilingWater's blog / some tips about __int128"
// https://codeforces.com/blog/entry/75044
// shortcomings AFAICT: console only, print fails for INT128_MIN,
// "atoi, atol, atoll" - https://en.cppreference.com/w/c/string/byte/atoi,
// "strtol, strtoll" - https://en.cppreference.com/w/c/string/byte/strtol,
// "strtoul, strtoull" - https://en.cppreference.com/w/c/string/byte/strtoul,
// tried to: xxxx
// - not yet covered:
// Read multiple signs ( ++x, +-x ... ),
// Other print formatting.
// Other roots, no binary, octal or hex ...
// In contrast to snprintf no length limit.
// To know: defined behaviour, under- / overflows throw error and return
// MAX in that direction. Like strto(u)l, unlike wrapping of atoi.
// Caution: in contrast to overflow check for conversions calculations do wrap!
// To know: signed and unsigned are not different, just different interpretation
// of the same bitstring.
// Build: gcc or some c-compiler / libc with int128 support.
// Build / tested: linux ( debian ), GNU gcc, glibc 2.40, Intel xeon hardware.
// Standalone: compile with 'gcc -O2 -o int128_test_5 int128_test_5.c',
// ignore warning about 'integer constant is so large that it is unsigned'.
// Run with: './int128_test_5 xxxx (yy)' where xxxx is a string to read, convert,
// re-convert and print. yy is optional to set the iterations for timing.
// The macros before 'main' provide the functionality,
// main has usage examples, tests some edge cases, compares under- / overflow
// to strtoul, strtol and atoi, and checks arbitrary input from command line,
// as well it provides simple performance timing.
// Use in other programs: copy/paste from '#include' .. '#define TIMEITcu' into
// your program, check for keyword or variable collisions test and then enjoy.
// The rest is commented or - IMHO - self-explanatory.
#include <errno.h> // reg. e.g. errno,
#include <stdio.h> // reg. e.g. printf, putchar,
#include <string.h> // reg. e.g. strcpy,
#include <stdbool.h> // reg. e.g. bool,
#include <limits.h> // reg. e.g. UINT_MAX,
#include <stdlib.h> // reg. e.g. strtoul,
#include <time.h> // reg. e.g. clock(),
#include <stdint.h> // reg. e.g. __int128,
#include <wchar.h> // reg. e.g. wcstol,
#include <inttypes.h> // reg. e.g. imaxabs,
#define _GNU_SOURCE // acc. anonchatGTP required to use wchar.h, doesn't help for that,
// #define int128 long long // doesn't work,
// #define int128 int128_t // doesn't work,
// #define int128 int_128 // doesn't work,
#define int128 __int128 // try to avoid int128, __int128, __int128_t confusions,
#define uint128 unsigned __int128 // try to avoid long word 'unsigned',
// #define int128 __int128_t // try to avoid int128, __int128, __int128_t confusions,
// #define uint128 __uint128_t // try to avoid long word 'unsigned',
// not yet found which of above pairs is better,
#define uint unsigned int // try to avoid long word 'unsigned',
#define ulong unsigned long // try to avoid long word 'unsigned',
#define U128_MIN 0
#define U128_MAX ( ( ( (uint128)( 0xFFFFFFFFFFFFFFFF ) ) << 64 ) + 0xFFFFFFFFFFFFFFFF ) // gcc doesn't support constants > 64-bit?,
#define U128_MAX_DIV10 ( U128_MAX / 10 ) // constants for overflow check,
#define U128_MAX_MOD10 ( U128_MAX % 10 )
#define I128_MAX ( ( ( (int128)( 0x7FFFFFFFFFFFFFFF ) ) << 64 ) + 0xFFFFFFFFFFFFFFFF ) // gcc doesn't support constants > 64-bit?,
#define I128_MAX_DIV10 ( I128_MAX / 10 ) // constants for overflow check,
#define I128_MAX_MOD10 ( I128_MAX % 10 )
#define I128_MIN ( -I128_MAX - 1 ) // gcc doesn't support constants > 64-bit?,
#define I128_MIN_DIV10 ( I128_MIN / 10 ) // constants for overflow check,
#define I128_MIN_MOD10 ( I128_MIN % 10 )
// volatile int i = 0; // global variables defined before 'macros',
// char str[ 45 ];
// char *ptr = str;
clock_t start1, end1;
double reference = 1;
uint128 strtou128( const char *s ) { // string to uint128,
const char *p = s;
uint128 val = 0;
while ((*p == '\n') || (*p == '\t') || (*p == ' ') || // Skip leading whitespace
(*p == '\f') || (*p == '\r') || (*p == '\v'))
p++;
if( *p == '-' ) { // Check against negative,
errno = 1;
perror( "error, don't try to convert negative string into positive value" );
return( 0 );
}
if( *p == '+' ) // Swallow '+',
p++;
while (*p >= '0' && *p <= '9') { // Convert string to number
if( ( val > U128_MAX_DIV10 ) || // This seems costly, carry a counter and start at digit37?
( ( val == U128_MAX_DIV10 ) && ( ( *p - '0' ) > U128_MAX_MOD10 ) ) ) {
errno = ERANGE;
perror( "error, uint128 overflow" );
return( U128_MAX );
}
val = (10 * val) + (*p - '0');
p++;
}
return val;
}
int128 strtoi128( const char *s ) { // string to int128 from ???,
const char *p = s;
int128 val = 0;
bool neg = 0;
while ((*p == '\n') || (*p == '\t') || (*p == ' ') || // Skip leading whitespace
(*p == '\f') || (*p == '\r') || (*p == '\v'))
p++;
if( ( *p == '-' ) || ( *p == '+' ) ) { // Check for sign
neg = ( *p == '-' ); // account '-',
p++; // swalow '+',
}
if( !neg ) {
while (*p >= '0' && *p <= '9') { // Convert string to number,
if( ( val > I128_MAX_DIV10 ) ||
( ( val == I128_MAX_DIV10 ) && ( ( *p - '0' ) > I128_MAX_MOD10 ) ) ) {
errno = ERANGE;
perror( "error, int128 overflow" );
return( I128_MAX );
}
val = (10 * val) + (*p - '0');
p++;
}
}
if( neg ) {
if( *p >= '0' && *p <= '9' ) { // Account 'neg',
val = ( ( 10 * val ) - ( *p - '0' ) );
p++;
}
while (*p >= '0' && *p <= '9') { // Convert string to number,
if( ( val < I128_MIN_DIV10 ) ||
( ( val == I128_MIN_DIV10 ) && ( ( *p - '0' ) > -I128_MIN_MOD10 ) ) ) {
errno = ERANGE;
perror( "error, int128 underflow" );
return( I128_MIN );
}
val = ( 10 * val ) - ( *p - '0' );
p++;
}
}
return val;
}
char* u128tostr( uint128 x ) { // converting uint128 into 0-terminated ASCII string, buffer approach,
static char arr[ 40 + 1 ] = { 0 }; // definition includes termination,
int j = 40; // start from right,
while( x > 9 ) { // iterate through value,
arr[ --j ] = ( x % 10 + '0' ); // set next digit,
x /= 10; // strip from value,
}
arr[ --j ] = ( x + '0'); // set last ( most significant ) digit,
return arr + j; // done, but if that works?
}
char* i128tostr( int128 x ) { // converting int128 into 0-terminated ASCII string, buffer approach,
static char arr[ 41 + 1 ] = { 0 }; // definition includes termination, needs one add. byte for the sign,
int j = 41; // start from right,
bool neg = ( x < 0 );
if( neg ) {
arr[ --j ] = -( x % 10 ) + '0'; // set next digit,
x = x / 10; // strip from value,
x = -x; // strip from value,
} else {
arr[ --j ] = x % 10 + '0'; // set next digit,
x = x / 10; // strip from value,
}
while( x > 9 ) { // iterate through value,
arr[ --j ] = ( x % 10 + '0' ); // set next digit,
x /= 10; // strip from value,
}
if( x > 0 ) // don't ad leading 0 to 1 digit negative values,
arr[ --j ] = ( x + '0'); // set last ( most significant ) digit,
if( neg )
arr[ --j ] = '-'; // add sign,
return arr + j; // done, but if that works?
}
#define TIMEITcu( expr, N, comment ) \
start1 = clock(); \
for( int i = 1; i <= N; i++ ) \
{ \
expr; \
} \
end1 = clock(); \
printf( "%07d; %09.03f; %1d; '%1u; %s; %s \n", end1 - start1, ( end1 - start1 ) / reference, N, x1u, #expr, comment )
#define TIMEITci( expr, N, comment ) \
start1 = clock(); \
for( int i = 1; i <= N; i++ ) \
{ \
expr; \
} \
end1 = clock(); \
printf( "%07d; %09.03f; %1d; '%1d; %s; %s \n", end1 - start1, ( end1 - start1 ) / reference, N, x1i, #expr, comment )
#define TIMEITclu( expr, N, comment ) \
start1 = clock(); \
for( int i = 1; i <= N; i++ ) \
{ \
expr; \
} \
end1 = clock(); \
printf( "%07d; %09.03f; %1d; '%1lu; %s; %s \n", end1 - start1, ( end1 - start1 ) / reference, N, x1lu, #expr, comment )
#define TIMEITcli( expr, N, comment ) \
start1 = clock(); \
for( int i = 1; i <= N; i++ ) \
{ \
expr; \
} \
end1 = clock(); \
printf( "%07d; %09.03f; %1d; '%1ld; %s; %s \n", end1 - start1, ( end1 - start1 ) / reference, N, x1li, #expr, comment )
#define TIMEITcllu( expr, N, comment ) \
start1 = clock(); \
for( int i = 1; i <= N; i++ ) \
{ \
expr; \
} \
end1 = clock(); \
printf( "%07d; %09.03f; %1d; '%s; %s; %s \n", end1 - start1, ( end1 - start1 ) / reference, N, u128tostr( x1llu ), #expr, comment )
#define TIMEITclli( expr, N, comment ) \
start1 = clock(); \
for( int i = 1; i <= N; i++ ) \
{ \
expr; \
} \
end1 = clock(); \
printf( "%07d; %09.03f; %1d; '%s; %s; %s \n", end1 - start1, ( end1 - start1 ) / reference, N, i128tostr( x1lli ), #expr, comment )
#define TIMEITcstr( expr, N, comment ) \
start1 = clock(); \
for( int i = 1; i <= N; i++ ) \
{ \
expr; \
} \
end1 = clock(); \
printf( "%07d; %09.03f; %1d; '%s; %s; %s \n", end1 - start1, ( end1 - start1 ) / reference, N, str1, #expr, comment )
int main( int argc, char *argv[] ) {
volatile uint128 x1llu;
volatile int128 x1lli;
volatile ulong x1lu;
volatile long x1li;
volatile uint x1u;
volatile int x1i;
char *str1;
int count = 1000;
wchar_t wide_str[20];
if( argv[ 2 ] ) count = atoi( argv[ 2 ] );
printf( " \n" );
printf( "check availability of INT_128 \n\n" );
#ifdef __SIZEOF_INT128__
printf( "size of INT128: %1d \n", __SIZEOF_INT128__ );
#else
printf( "INT128 likely not supported. \n" );
#endif
printf( "sizeof( intmax_t ) : %1d \n", sizeof( intmax_t ) );
printf( "sizeof( uintmax_t ): %1d \n", sizeof( uintmax_t ) );
printf( " \n" );
printf( "check constants / references \n\n" );
printf( "U128_MAX : %s \n", u128tostr( U128_MAX ) );
printf( "U128_MAX_DIV10 : %s \n", u128tostr( U128_MAX_DIV10 ) );
printf( "U128_MAX_MOD10 : %s \n", u128tostr( U128_MAX_MOD10 ) );
printf( " \n" );
printf( "I128_MAX : %s \n", i128tostr( I128_MAX ) );
printf( "I128_MAX_DIV10 : %s \n", i128tostr( I128_MAX_DIV10 ) );
printf( "I128_MAX_MOD10 : %s \n", i128tostr( I128_MAX_MOD10 ) );
printf( " \n" );
printf( "I128_MIN : %s \n", i128tostr( I128_MIN ) );
printf( "I128_MIN_DIV10 : %s \n", i128tostr( I128_MIN_DIV10 ) );
printf( "I128_MIN_MOD10 : %s \n", i128tostr( I128_MIN_MOD10 ) );
printf( " \n" );
printf( "testing uint128: \n\n" );
x1llu = 1; // simple case,
printf( "simple 'x1llu = 1' : %s \n", u128tostr( x1llu ) ); // print as string,
x1llu--; // simple calculation, and checking 0,
printf( "'x1llu--' : %s \n", u128tostr( x1llu ) );
x1llu--; // simple calculation, and checking wrap below 0,
printf( "'x1llu--' wrap below 0 : %s \n", u128tostr( x1llu ) );
printf( " \n" );
x1llu = U128_MIN; // checking MIN,
printf( "U128_MIN : %s \n", u128tostr( U128_MIN ) );
printf( " \n" );
x1llu = U128_MAX; // checking MAX,
printf( "U128_MAX : %s \n", u128tostr( x1llu ) );
x1llu++; // check wrap above MAX,
printf( "'x1llu++' wrap above MAX: %s \n", u128tostr( x1llu ) );
printf( " \n" );
printf( "testing int128: \n\n" );
x1lli = 1; // simple case,
printf( "simple 'x1ll1 = 1' : %s \n", i128tostr( x1lli ) ); // print the string,
x1lli--; // simple calculation, and checking 0,
printf( "'x1ll1--' testing 0 : %s \n", i128tostr( x1lli ) ); // print the string,
x1lli--; // checking advaning into negtive,
printf( "'x1ll1--' go to negative: %s \n", i128tostr( x1lli ) ); // print the string,
printf( " \n" );
x1lli = I128_MIN; // checking MIN,
printf( "'x1ll1 = I128_MIN' : %s \n", i128tostr( x1lli ) ); // print the string,
x1lli--; // checking wrap below MIN,
printf( "'x1ll1--' wrap below MIN: %s \n", i128tostr( x1lli ) ); // print the string,
printf( " \n" );
x1lli = I128_MAX; // checking MAX,
printf( "'x1ll1 = I128_MAX' : %s \n", i128tostr( x1lli ) ); // print the string,
x1lli++; // checking wrap above MAX,
printf( "'x1ll1++' wrap above MAX: %s \n", i128tostr( x1lli ) ); // print the string,
printf( " \n" );
printf( "'ll' wraps at LONG_MAX : %s \n\n", i128tostr( -9223372036854775808ll ) ); // print the string,
printf( "'ll' wraps at LONG_MAX : %s \n\n", i128tostr( -18446744073709551616ll ) ); // print the string,
printf( "cross check vs. unsigned long int: \n\n" );
x1lu = 1; // simple case,
printf( "simple 'x1lu = 1' : %1lu \n", x1lu ); // print the value,
x1lu--; // simple calculation, and checking 0,
printf( "'x1lu--' : %1lu \n", x1lu );
x1lu--; // simple calculation, and checking wrap below 0,
printf( "'x1lu--' wrap below 0 : %1lu \n", x1lu );
printf( " \n" );
x1lu = ULONG_MAX; // checking UINT_MAX,
printf( "'ULONG_MAX' : %1lu \n", x1lu );
x1lu++; // checking wrap above MAX,
printf( "'x1lu++' wrap above MAX : %1lu \n", x1lu );
printf( " \n" );
printf( "cross check vs. long int: \n\n" );
x1li = 1; // simple case,
printf( "simple 'x1li = 1' : %1ld \n", x1li ); // print the value,
x1li--; // simple calculation, and checking 0,
printf( "'x1li--' : %1ld \n", x1li );
x1li--; // simple calculation, and checking advance into negative,
printf( "'x1li--' advance to neg.: %1ld \n", x1li );
printf( " \n" );
x1li = LONG_MIN; // checking INT_MIN,
printf( "'LONG_MIN' : %1ld \n", x1li );
x1li--; // check wrap below MIN,
printf( "'x1li--' wrap below MIN : %1ld \n", x1li );
printf( " \n" );
x1li = LONG_MAX; // checking INT_MAX,
printf( "'LONG_MAX' : %1ld \n", x1li );
x1li++; // check wrap above MAX,
printf( "'x1li++' wrap above MAX : %1ld \n", x1li );
printf( " \n" );
printf( "cross check vs. unsigned int: \n\n" );
x1u = 1; // simple case,
printf( "simple 'x1u = 1' : %1u \n", x1u ); // print the value,
x1u--; // simple calculation, and checking 0,
printf( "'x1u--' : %1u \n", x1u );
x1u--; // simple calculation, and checking wrap below 0,
printf( "'x1u--' wrap below 0 : %1u \n", x1u );
printf( " \n" );
x1u = UINT_MAX; // checking UINT_MAX,
printf( "'UINT_MAX' : %1u \n", x1u );
x1u++; // checking wrap above MAX,
printf( "'x1u++' wrap above MAX : %1u \n", x1u );
printf( " \n" );
printf( "cross check vs. int: \n\n" );
x1i = 1; // simple case,
printf( "simple 'x1i = 1' : %1d \n", x1i ); // print the value,
x1i--; // simple calculation, and checking 0,
printf( "'x1i--' : %1d \n", x1i );
x1i--; // simple calculation, and checking advance into negative,
printf( "'x1i--' advance to neg. : %1d \n", x1i );
printf( " \n" );
x1i = INT_MIN; // checking INT_MIN,
printf( "'INT_MIN' : %1d \n", x1i );
x1i--; // check wrap below MIN,
printf( "'x1i--' wrap below MIN : %1d \n", x1i );
printf( " \n" );
x1i = INT_MAX; // checking INT_MAX,
printf( "'INT_MAX' : %1d \n", x1i );
x1i++; // check wrap above MAX,
printf( "'x1i++' wrap above MAX : %1d \n", x1i );
printf( " \n" );
printf( "Check arbitrary values from command line argument: \n\n", x1li );
x1llu = strtou128( argv[ 1 ] );
printf( "input as uint128 : %s \n", u128tostr( x1llu ) );
printf( "under- / overflow error < 0 and > U128_MAX, \n\n" );
printf( "performance of uint128: \n" );
TIMEITcllu( x1llu = strtou128( argv[ 1 ] ), count, "" );
TIMEITcstr( str1 = u128tostr( x1llu ), count, "" );
printf( " \n" );
x1lli = strtoi128( argv[ 1 ] );
printf( "input as int128 : %s \n", i128tostr( x1lli ) );
printf( "under- / overflow error < I128_MIN and > I128_MAX \n\n" );
printf( "performance of int128: \n" );
TIMEITclli( x1lli = strtoi128( argv[ 1 ] ), count, "" );
TIMEITcstr( str1 = i128tostr( x1lli ), count, "" );
printf( " \n" );
x1lu = strtoul( argv[ 1 ], NULL, 10 );
printf( "input as unsig. longint : %1lu \n", x1lu );
printf( "strtoul works up to ULONG_MAX, then stuck, \n" );
printf( "in negative wraps down to -ULONG_MAX, then stuck there, \n\n" );
printf( "performance of ulong: \n\n" );
TIMEITclu( x1lu = strtoul( argv[ 1 ], NULL, 10 ), count, "" );
TIMEITcstr( snprintf( str1, 21, "%lu", x1lu ), count, "" );
printf( " \n" );
x1li = strtol( argv[ 1 ], NULL, 10 );
printf( "input as long int : %1ld \n", x1li );
printf( "strtol works up to LONG_MAX, then stuck, \n" );
printf( "in negative wraps down to LONG_MIN, then stuck there, \n\n" );
printf( "performance of long: \n\n" );
TIMEITcli( x1li = strtol( argv[ 1 ], NULL, 10 ), count, "" );
TIMEITcstr( snprintf( str1, 21, "%ld", x1li ), count, "" );
printf( " \n" );
x1u = atoi( argv[ 1 ] );
printf( "input as uint : %1u \n", x1u );
printf( "atoi ( found no atou ) wrapping up to LONG_MAX, then stuck at \n" );
printf( "4294967295 ( UINT_MAX ), in negative it wraps down to \n" );
printf( "LONG-MIN, then stuck at 0. \n\n" );
printf( "performance of uint: \n\n" );
TIMEITcu( x1u = atoi( argv[ 1 ] ), count, "" );
TIMEITcstr( snprintf( str1, 11, "%u", x1u ), count, "" );
printf( " \n" );
x1i = atoi( argv[ 1 ] );
printf( "input as int : %1d \n", x1i );
printf( "atoi wraps up to LONG_MAX, then stuck at -1 \n" );
printf( "in negative it wraps down to LONG_MIN, then stuck at 0. \n\n" );
printf( "performance of int: \n\n" );
TIMEITci( x1i = atoi( argv[ 1 ] ), count, "" );
TIMEITcstr( snprintf( str1, 12, "%d", x1i ), count, "" );
printf( " \n" );
// acc. anonchatGPT compiling with '-std=c99 helps to use wchar.h types ... not yet got it to work ...
/// wide_str = \0;
/// x1llu = wcstoull( argv[ 1 ], wide_str, 10 );
/// u128tostr( str, x1llu );
/// printf( "input as uint128 : %s \n", str );
printf( "done \n \n" );
return 0;
}
printf(). Sadly, the GNU library doesn't seem to support registering a correspondingscanf()format. \$\endgroup\$BitIntinteger types available now with C23, perhaps we should design 4 functions: signed to/from string, unsigned to/from string, that all take a bit-widthnargument. This 128-bit set of yours is good, yet arbitrary width-nis not that much of an extension. Hmmm, maybe*printf()was already extended for that...? \$\endgroup\$