[edit] Hint: a similar project for C++20 which - as far as I see - also manages bigger than 128-bit integers can be found at: Infinite precision integer in C++20 . [/edit]
I got lots of kind hints on prior steps in:
int128 handling in c-code, gcc / glibc / linux
int128 handling in c-code, gcc / glibc / linux - follow up
int128 handling in c-code, gcc / glibc / linux - follow up II
int128 handling in c-code, gcc / glibc / linux - follow up III,
and now with adding read from and write to other formats ( hex, oct, bin ) and
some simple math routines like abs(olute), neg(ate), nextafters, round and rint
from fractional formats it - IMHO - overtaxes the size which can be handeled
here.
Thus I tried to put it into a gitlab repository:
libint128
( also meant for users who consider it matured enough and meaningful to use in
their projects ), and think to continue there.
As of now I have:
A "header only" library, libint128.h, providing the functionality,
a pdf document, libint128_a.pdf, describing use and intentions, and
a test program, int128_test_5.c, which is just poking around, not qualified for
a review, but can help in understanding the library.
Questions are:
Header only: I got the hint to split in *.h and *.c, for the moment it looks
easy to me to have a "one point" approach. Is there substantial need to take the
burden to split, deal with "make", compile to and "install" a library, keep two
sources in sync and so on?
Readability: now keeping the formatting stable by spaces instead of tabs, see
snippets below, is the code read- and acceptable as "over commented" but
meaningful separated?
Over commenting, comments as hints for myself, deactivated console error logs,
trailing spaces ... pls. just ( try to ) ignore, as well pls. ignore shortcomings in
the test program.
Redundancy: there are e.g. ten read routines, eight distinct from 4 types of
origin into two types of target and two trying "type aware" ( weak for input
with leading zeroes ). It was already mentioned to evtl. boil down by
abstracting
and parametricizing the constants.
I didn't find a good way to balance readability and length, doe's someone have
an idea or can point to a good example?
Coverage: think I have seen some standard routines handling arbitrary roots from
2 to 36? Is that meaningful? Do I need to mimic?
Quality: is the code meaningful, understandable and stable enough to invite
others to use it? Or are there flaws / hints I forgot to account?
Helper functions: I defined "isbdigit" and "isodigit", see in the snippets
below, isn't similar available in standard libraries?
Math functions: Is the implementation meaningful?
Other datatypes: Is there a meaningful way to have the code for e.g. round from
decimal datatypes in the lib., but avoid compile and warnings if libdfp is not
installed?
Read other formats: is it meaningful and functional safe to skip formatting characters like in 1 000 000? Meaningful and can be safely implemented to accept formats like "scientific"? 1.25E+02 is an integral value. Meaningful to read from fractional strings and round acc. the fraction part?
Localization: is it meaningful and can it be safely implemented to adapt to locale specific properties, e.g. in read scientific "," vs. "."?
Print other formats: is it meaningful and is there a predefined path to print in e.g. scientific or "thousands separated" formats?
Any help, hints, tricks welcome.
As I'm also new in "gitlabbing" I expect to make mistakes, so I reserve the
right to change or delete without notice, but will most likely create new with
the same or similar name.
Snippets:
Read routines, here octal to int128:
// ----------------------------------------
int128 ascotoi128( const char *s ) {                                                    // Octal string to int128. 
        const char *p = s;                                                              // Position pointer in source. 
        int128 val = 0;                                                                 // Target. 
        bool neg = 0;                                                                   // Marker for negative. 
        while( isspace( *p ) ) {                                                        // Skip leading whitespace. 
                p++; 
        } 
 
        while( ( *p == '-' ) || ( *p == '+' ) ) {                                       // Check signs, accepts multiple. 
                if( *p == '-' ) {                                                       // Account '-' by toggeling. 
                        neg = !neg; 
                } 
                p++;                                                                    // Swallow '+'. 
        } 
 
        if( *p == '0' ) {                                                               // Skip datatype prefix. 
                p++; 
        } 
 
        if( !isodigit( *p ) ) {                                                         // Capture invalid strings like "-", " -literals". 
                errno = ERANGE; 
//                 perror( "invalid input, result set to zero" );                       // optional console log if issue in conversion, 
                return( 0 ); 
        } 
 
        while( isodigit( *p ) ) {                                                       // Convert decimal string to number. 
                if( val <= I128_MIN_DIV8 && 
                    ( val < I128_MIN_DIV8  || ( *p - '0' > -( I128_MIN_MOD8 ) ) ) ) { 
                        errno = ERANGE; 
                        if( neg ) { 
//                                 perror( "error, int128 underflow, returning MIN" );  // optional console log if issue in conversion, 
                                return( I128_MIN ); 
                        } else { 
//                                 perror( "error, int128 overflow, returning MAX" );   // optional console log if issue in conversion, 
                                return( I128_MAX ); 
                        } 
                } 
                val = ( 8 * val ) - ( *p - '0' ); 
                p++; 
        } 
 
        if( !neg ) { 
                if( val == I128_MIN ) {                                                 // We have one less positive than negative value. 
                        errno = ERANGE; 
//                      perror( "error, int128 overflow, returning MAX" );              // optional console log if issue in conversion, 
                        return( I128_MAX ); 
                } else { 
                        val = -val; 
                } 
        } 
 
        return( val ); 
} 
// ----------------------------------------
Output routines, here int128 to decimal:
// ----------------------------------------
char* i128toascd( char* str, size_t len, int128 x ) {                                   // Int128 to 0-terminated decimal ASCII string. 
// For int128 this version is preferred over a do - while loop, it reproducibly 
// has better performance for small values ( less than 16 digits ), while head 
// to head above. 
// Absolute performance: ~factor 3 slow vs. asctoi128 and snprintf( long-x ), 
// reg. divisions? reg. calculating in negative? 
// Assumption and tested: the division is the costly operation, indeed '/ 10' 
// is factor ~3 slow for int128 vs. long and int, even if the argument to divide 
// is zero, such is business for others. 
        size_t j = len;                                                                 // Start from right. 
        str[ --j ] = '\0';                                                              // Terminate the string. 
        bool neg = x < 0; 
 
        x = ( x < 0 ) 
                ? x 
                : -x; 
 
        while( x < -9 ) {                                                               // Iterate through value. 
                str[ --j ] = (char)( -( x % 10 ) + '0' );                               // Set next digit. 
                x /= 10;                                                                // Strip from value. 
        } 
 
        str[ --j ] = (char)( -x + '0' );                                                // Set last ( most significant ) digit. 
 
        if( neg ) { 
                str[ --j ] = '-'; 
        } 
 
        memmove( str, str + j, len - j );                                               // Relocate the result to start of target. 
 
        return( str );                                                                  // Done, looks working well. 
} 
// ---------------------------------------- enter link description here
Helper functions, here "isodigit":
// ----------------------------------------
bool isodigit( char c ) {                                                               // Check if octal digit, '0' .. '7'. 
        return( isdigit( c ) && c <= '7' ); 
} 
// ----------------------------------------
Math functions, here round __float128 to int128:
// ----------------------------------------
int128 i128roundq( __float128 x1q ) {                                                   // Rounds a binary128 value to a signed 128-bit integer, 'ties away'. 
 
        if( x1q < I128_MIN ) { 
                errno = ERANGE; 
//              perror( "argument underflow, result set to MIN" );                      // optional console log if issue in conversion, 
                return( I128_MIN ); 
        } else if( x1q > I128_MAX ) { 
                errno = ERANGE; 
//              perror( "argument overflow, result set to MAX" );                       // optional console log if issue in conversion, 
                return( I128_MAX ); 
        } else if( fabsq( x1q) >= 5192296858534827628530496329220096.0q ) {             // integral range of binary128, avoid pre-rounding in add snap mis-rounding, 
                return( (int128)x1q ); 
        } else { 
                return( ( x1q < 0 ) 
                        ? -(int128)truncq( -x1q + 0.49999999999999999999999999999999995q ) // Not padding with 0.5 to avoid roundup of 0.49999999999999999999999999999999995q. 
                        :  (int128)truncq(  x1q + 0.49999999999999999999999999999999995q ) ); 
        } 
} 
// ----------------------------------------
