1

I need a 12 byte or 96 bit unsigned integer to do simple arithmetic operations on it. I tried with the below approach, Since there is no builtin data type in C.

typedef struct
{
    __uint128_t i:96;
    __uint128_t j:96;
};
__uint128_t __add(__uint128_t a, __uint128_t b) { return a + b; }

But this is not being supported with some older versions of GCC compiler versions,

Hence, a generic implementation is required with a cross compilation support.

According to the answer mentioned here- Any predefined type for unsigned 96 bit integer?

An array of 3 integers, as an alternate solution? How?

Any further help is greatly appreciated !

  • 1
    How about using something like the GMP multiprecision arithmetic library? – Barmar Dec 20 '19 at 06:41
  • 3
    *Why* do you need 96-bit integers and do arithmetic on them? What is the *real* problem you need to solve? – Some programmer dude Dec 20 '19 at 06:42
  • need to store a nano second value –  Dec 20 '19 at 06:43
  • 6
    If you need to store nanosecond values, then store whole seconds in a 64-bit integer and the fractional part in a 32-bit integer. The arithmetic for that is not too hard. That's what a `struct timespec` does in practical terms. You do have to decide whether you'll need signed values (negative intervals between two nanosecond time values). Handling a sign bit is trickier (maybe use a 1-bit sign and 63-bit magnitude field out of the 64-bits for the whole seconds). – Jonathan Leffler Dec 20 '19 at 06:44
  • Maybe tis helps: https://stackoverflow.com/questions/11475320/any-predefined-type-for-unsigned-96-bit-integer – Mike Dec 20 '19 at 07:20
  • I think the number of `__` is incorrect. You should use a different number. – Yunnosch Dec 20 '19 at 07:24
  • Does this answer your question? [Any predefined type for unsigned 96 bit integer?](https://stackoverflow.com/questions/11475320/any-predefined-type-for-unsigned-96-bit-integer) – Dblaze47 Dec 20 '19 at 07:25
  • @Yunnosch- it is working example for gcc (GCC) 4.8.5 –  Dec 20 '19 at 07:27
  • @ Dblaze47- The answer posted in the link is interesting, however there is no cross compilation support for it :( https://stackoverflow.com/questions/11475320/any-predefined-type-for-unsigned-96-bit-integer –  Dec 20 '19 at 07:29
  • 1
    @ThulasiVeggalam And which is the 96 bit computer you need to cross-compile to? ...seriously, just use a "bigint" library. – Lundin Dec 20 '19 at 07:47
  • @Lundin - It is not 96 bit computer, but it is 32/64 bit one, just need to express the numbers in 96 bits –  Dec 20 '19 at 09:16
  • https://en.wikipedia.org/wiki/GNU_Multiple_Precision_Arithmetic_Library – Lundin Dec 20 '19 at 09:23
  • 1
    @Lundin: A multi-precision library like that is an extreme amount of unnecessary bloat. The meta-data alone (e.g. number of digits, amount of space) would cost more than the data, and performance will suck due to unnecessary conditions. – Brendan Dec 20 '19 at 10:34
  • 1
    @Brendan Yeah so then don't use 96 bit numbers, problem solved. I'm not sure what application that needs higher resolution than 18.44*10^18, but it won't be a performance-critical one. – Lundin Dec 20 '19 at 10:36
  • @Lundin: I use a "32-bit source, 32-bit seconds, 64-bit fractions of a second" format for timestamps used for logging/tracing. It gets pounded (every action logged). – Brendan Dec 20 '19 at 10:45
  • 1
    It seems "struct timespec" is the best solution for this –  Dec 20 '19 at 11:06
  • @Brendan *used for logging/tracing. It gets pounded* How do you get your timestamp? `struct timespec ts; clock_gettime( CLOCK_MONOTONIC_HR, &ts );`? If it "gets pounded", why are you transforming the data in the pipeline that "gets pounded? Write the raw timestamp and post-process it **outside** of the "pounded" processing path. – Andrew Henle Dec 20 '19 at 13:11
  • @AndrewHenle: I have wrappers to convert to/from `struct timespec` and `TIME` on the C side, and "`System.nanoTime() + offset_determined_at_startup`" on the java side; where both sides try to fix the utter brokenness of leap second handling that would otherwise make things like finding the difference between times impossible to do reliably. – Brendan Dec 20 '19 at 18:55
  • @Brendan And you've still not stated any reason why such processing can't be done in a post-processing phase and not in any processing pipeline that "gets pounded". If performance is that critical, remove processing that doesn't have to be in the critical path - the calculations will be the same. – Andrew Henle Dec 20 '19 at 23:02
  • @AndrewHenle: Standard practice is to bury "festering pile of idiocy" under wrappers as soon as possible and confine the resulting "#idef" snake's nest (needed to abstract things like whether or not `struct timespec` exists, and how leap seconds are dealt with by the OS) to prevent the spread of the infection. In my case, there's a network protocol and file format where the specs dictate the time format (for all software, in any language, on any OS), and the data must be converted before sending or storing (and before processing), and before it's impossible to handle leap seconds. – Brendan Dec 21 '19 at 01:13
  • @Brendan If you know the time of the first call to `clock_gettime( CLOCK_MONOTONIC_HR, &ts );`, the conversion of all subsequent samples to a date and time is completely independent of how any OS handles leap seconds. If you're converting bits into a printable/readable file format and then carrying that format all the way through the processing, it's not "pounded". – Andrew Henle Dec 21 '19 at 01:54
  • @AndrewHenle: That just makes more mess (more silly `#ifdef ...` junk with more cases to maintain), because `CLOCK_MONOTONIC_HR` (and `CLOCK_MONOTONIC`) aren't guaranteed to be supported on POSIX systems (but `CLOCK_REALTIME` is implemented wherever `clock_gettime()` is supported). It's only converted to printable/readable for display purposes (in the Java client where I can take user's time zone into account). – Brendan Dec 21 '19 at 03:23

3 Answers3

4

The easiest way would be to use a uint128_t unless it is unavailable on your platform or the extra storage used is somehow a problem for you.

That said, you can use an array of three uint32_ts as you mentioned in your question. But then you will need to implement all the basic arithmetic operations yourself and as you see, it can get quite complicated! Here is an example of just addition implemented.

#include <stdio.h>
#include <stdint.h>

typedef struct uint96_t 
{
    uint32_t value[3];
}uint96_t;

uint96_t add_uint96(uint96_t x, uint96_t y)
{
    uint96_t result = {0};
    unsigned int carry = 0;
    int i = sizeof(x.value)/sizeof(x.value[0]);
    /* Start from LSB */
    while(i--)
    {
        uint64_t tmp = (uint64_t)x.value[i] + y.value[i] + carry;    
        result.value[i] = (uint32_t)tmp;
        /* Remember the carry */
        carry = tmp >> 32;
    }
    return result;
}

void print_uint96(char *str, uint96_t x)
{
    printf("%s = %08x%08x%08x\r\n", str, x.value[0], x.value[1], x.value[2]);
}

int main(void)
{
    uint96_t x = {0x0FFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
    uint96_t y = {0x00, 0x00, 0x01};
    uint96_t r = add_uint96(x, y);
    print_uint96("x", x);
    print_uint96("y", y);
    print_uint96("result", r);
    return 0;
}
th33lf
  • 2,177
  • 11
  • 15
  • long double - Real floating-point type, usually mapped to an extended precision floating-point number format. Actual properties unspecified. It can be either x86 extended-precision floating-point format (80 bits, but typically 96 bits or 128 bits in memory with padding bytes), the non-IEEE "double-double" (128 bits), IEEE 754 quadruple-precision floating-point format (128 bits), or the same as double. See the article on long double for details. –  Dec 21 '19 at 16:06
  • @ThulasiVeggalam: on some C implementations for x86-64, `long double` is the 80-bit x87 type. (e.g. compilers for Linux / MacOS). On Windows, `long double` is the same as 64-bit `double` (MSVC, and gcc/clang/ICC targeting Windows.) You don't very likely don't want extended-precision FP for your data. – Peter Cordes Dec 21 '19 at 20:29
  • @ThulasiVeggalam I don't think floating point is the right approach if you want a specific number of bits of storage. It is likely to involve even more complication and is not guaranteed to give you the required number of bits of precision. Like Peter mentioned above, it is also even more variable between platforms than integers are. – th33lf Dec 23 '19 at 10:55
1

If you need to use a generic solution you can try to create a struct that contains a 64 bit integer and another 32 bit integer. Then redefine some operation you need to do.

A classical example is the struct timespec that has 2 integer one for second and one for nanosecond.

Zig Razor
  • 3,381
  • 2
  • 15
  • 35
1

I have done something like this below with the help of struct timespec:

struct timespec
__timespec_add (const struct timespec start,
                      const struct timespec end)
{
  struct timespec sum;
  sum.tv_sec = start.tv_sec + end.tv_sec;
  sum.tv_nsec = start.tv_nsec + end.tv_nsec;
  if (sum.tv_nsec >= 1000000000)
  {
     ++sum.tv_sec;
     sum.tv_nsec = sum.tv_nsec - 1000000000;
  }
  return sum;
}

struct timespec
__timespec_diff (const struct timespec start,
                      const struct timespec end)
{
  struct timespec diff;
  diff.tv_sec = start.tv_sec - end.tv_sec;
  diff.tv_nsec = start.tv_nsec - end.tv_nsec;
  if (diff.tv_nsec < 0)
  {
      --diff.tv_sec;
      diff.tv_nsec = diff.tv_nsec + 1000000000;
  }
 return diff;
}
  • BTW, C11 specifies that `tv_nsec` is a signed `long`, so yes, subtraction can make it negative instead of wrapping to a high unsigned value. https://en.cppreference.com/w/c/chrono/timespec. – Peter Cordes Dec 23 '19 at 16:43