Initialize a uInt128 value from a string

Viewed 262

I how can i initialize a uint128 value from a string in C if there is no native uint128?

is this correct? if not how

struct uint128 {
    
    uint64_t left;
    uint64_t right;
};

void initializeUint128(const char *input, struct uint128 *output) {

    output->right = strtoull(input+16, NULL, 16);

    char *tmp = alloca(17);
    memcpy(tmp, input, 16);

    tmp[16] = '\0';
    output->left = strtoull(tmp, NULL, 16);
}
3 Answers

how can i initialize a uint128 value from a string in C if there is no native uint128?

First implement arithmetics of uint128 numbers. Implement addition and multiplication.

Then do a conversion like with any other datatype. Read one digit from the string, multiply number by 10, add it to number. Sample interface might look like this:

struct uint128 uint128_initialize_from_string_in_base10(const char *p, const char **endptr) {
     struct uint128 temp;
     uint128_initialize_zero(&temp);
     while (*p && isdigit(*p)) {
         char digit = *p++ - '0'; // or whatever base you want to have
         uint128_multiply_int(&temp, 10);
         uint128_add_int(&temp, digit);
     }
     if (endptr) *endptr = p;
     return temp;
}
     

is this correct?

It's syntactically correct. It's missing #include <stdint.h>.

if not how

Passing a string with length less then 16 is undefined

The code does not check for conversion errors.

The code assumes the input string is in base 16.

can you show me good way to have different digit lenght instead of fixed?

Use the second argument of strtoll to detect where the conversion has ended. I could see:

struct uint128 uint128_initialize_from_string_with_two_numbers_separated_by_whitespaces(
           const char *input) {
    const char *endptr;
    struct uint128 output;

    unsigned long long tempull = strtoull(input, &endptr, 0);
    // handle error
    output.left = tempull;

    tempull = strtoull(endptr, NULL, 0);
    // handle error
    output.right = tempull;

    return output;
}

First:

You're using a dynamic allocation for the tmp buffer in the initializeUint128 and it should be freed after the left member assignation. A better way here is to use a buffer on the stack with char tmp[17]; as suggested by @chux - Reinstate Monica.

Second:

As you're using fixed type integers uint64_t from C99, i guess your C compiler is C99 compliant.

Since C99, there's a generic support for 128bits integer through __int128 and unsigned __int128, even if the target CPU doesn't have native 128bits integer support.

Beware that this support does not extend to the standard C library. As an exemple, printf("%032X\n", (unsigned __int128)bigint); will only print the LSB (or MSB, depending on the Endianness of your target system) part of bigint. Thus you have to care about the Endianness in your case if you want to decompose the bigint, as for the case of the 0x000000000000001A0000000000102243 value below.

To this day, most C compiler can't compute a 128bits value directly (like unsigned __int128 value = 0x1A0000000000102244;) as there is not literal for it in the actual standard, you'll get a warning / error at compile time. But, for the 128bits initialization, note in the code below how this value is computed for the second_value variable. It could be a more appropriate way in your case than a string initialization.

To make it more clear, an example with your code :

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <malloc.h>
struct uint128 {

    uint64_t left;
    uint64_t right;
};

union uint128u { struct uint128 source; unsigned __int128 big; };

typedef union uint128u uint128_t;

void initializeUint128(const char *input, struct uint128 *output) {

    output->right = strtoull(input+16, NULL, 16);

    char tmp[17]; // char *tmp = alloca(17); see above why stack is more adequate here
    memcpy(tmp, input, 16);

    tmp[16] = '\0';
    output->left = strtoull(tmp, NULL, 16);
}
int main(int nbargs, char *args[]) {

   // Initialization through string in your structure uint128
   struct uint128 value;
   initializeUint128("000000000000001A0000000000102241", &value);
   printf("L:%016X R:%016X\n", value.left, value.right);

   // Initialization through string on your structure uint128 mirrored with __int128
   uint128_t bigvalue;
   initializeUint128("000000000000001A0000000000102242", &bigvalue.source);
   printf("L:%016X R:%016X\n", bigvalue.source.left, bigvalue.source.right);
   printf("The (wrong) whole with no int size specifier : %032X\n", bigvalue.big);
   printf("The (wrong) whole with ll : %032llX\n", bigvalue.big);

   // Initialization through shift uint64 + uint64 of __int128
   unsigned __int128 second_value = 0x1A;
   second_value = second_value<<64;
   second_value+=0x102243;
   uint128_t big2;
   big2.big=second_value;
   printf("L:%016X R:%016X\n", big2.source.left, big2.source.right);
   printf("The (wrong) whole second value : %032llX\n", second_value);
}

will print on a x86_64 (Little_Endian) :

L:000000000000001A R:0000000000102241
L:000000000000001A R:0000000000102242
The (wrong) whole with no int size specifier : 0000000000000000000000000000001A
The (wrong) whole with ll : 0000000000000000000000000000001A
L:0000000000102243 R:000000000000001A
The (wrong) whole second value : 00000000000000000000000000102243

Initialize a uInt128 value from a (hexadecimal) string

Find the string's length and convert the right-most 16 characters into output->right and leading ones, if any, into output->left.

void initializeUint128(const char *input, struct uint128 *output) {
    size_t len = strlen(input);
    if (len > 16) {
      if (sscanf(input, "%*" SCNx64 "%16" SCNx64, 
          (int) len - 16,  &output->left, &output->right) != 2) {
        Handle_Failure();
      }
    } else {
      output->left = 0;
      if (sscanf(input, "%" SCNx64, &output->right) != 1) {
        Handle_Failure();
      }
    }
 }

This approach does get fooled with in-between spaces.

Related