85

I need to store a 128 bits long UUID in a variable. Is there a 128-bit datatype in C++? I do not need arithmetic operations, I just want to easily store and read the value very fast.

A new feature from C++11 would be fine, too.

danijar
  • 32,406
  • 45
  • 166
  • 297
  • `std::bitset` might be useful. – ilent2 Aug 26 '13 at 08:11
  • I'd use a `std::vector` or event better a `std::array` if you have this available. – πάντα ῥεῖ Aug 26 '13 at 08:15
  • 3
    There's [libuuid](http://linux.die.net/man/3/libuuid) for linux available BTW. – πάντα ῥεῖ Aug 26 '13 at 08:18
  • You can also check out this link: http://stackoverflow.com/q/1188939/52568 – Kurt Pattyn Aug 26 '13 at 08:39
  • 11
    You're asking two different questions ... a 128-bit datatype doesn't need to be a 128-bit integer. Just use a struct consisting of two 64-bit integers, or any other combination that adds up to 128 bits. – Jim Balter Aug 26 '13 at 08:40
  • @JimBalter I want to use a standard type. Of course you can always write class handling longer types, but that is not my intention and thus not my question. – danijar Aug 26 '13 at 08:43
  • A struct is a standard type. So is a 64 bit int. And that *is* your question, because there's no standard 128 bit int in C++ ... duh. I said nothing about writing a class to handle longer types ... structs are first class entities; you can pass them as arguments, return them, copy them as if they were primitives. But go ahead, reject the right answer, I don't care. – Jim Balter Aug 26 '13 at 08:45
  • @JimBalter Okay, my question was if there is such a type and it isn't. That is a valid answer, of course. I wouldn't consider struct or class as a standard type, but as a way to create new (non standard) types. – danijar Aug 26 '13 at 08:47
  • " I wouldn't consider struct or class as a standard type" -- Then you have no clue. Sorry. A 128-bit struct meets all your requirements. – Jim Balter Aug 26 '13 at 08:49
  • 7
    @JimBalter: If a user-defined struct is a standard type, what exactly would qualify as a non-standard type? – Benjamin Lindley Aug 26 '13 at 08:53
  • "my question was if there is such a type" -- read this: http://meta.stackexchange.com/questions/182266/how-much-research-effort-is-expected-of-stack-overflow-users/ – Jim Balter Aug 26 '13 at 08:53
  • @JimBalter Could I encourage you to write an answer? I don't consider myself as a C++ professional, it may be true that I have no clue about this. – danijar Aug 26 '13 at 08:54
  • @BenjaminLindley The problem here is that the OP doesn't know what he's doing and has an XY Problem. The question was "Is there a 128-bit datatype in C++?" -- the answer is yes; "standard" is a red herring, a bit of confused and mistaken terminology. Properly speaking, the only "non-standard" types are implementation-defined extensions. – Jim Balter Aug 26 '13 at 08:57
  • danijar, you said my answer doesn't answer your question. I'm going to accept that and go find something better to do. Ta ta. – Jim Balter Aug 26 '13 at 08:59
  • @JimBalter That is your choice. I think I misunderstood your idea. Anyway, I'd like to understand your idea and therefore, you could help me by explaining it in an answer to this question. – danijar Aug 26 '13 at 09:02
  • 2
    Definitely an XY problem. Simply said, a "128 bit integer type" is a "128 bit type with the usual integer arithmetic operators". UUID's do not need arithmetic operators; `UUID * 42` does not make sense. However, `operator==` _is_ relevant. – MSalters Aug 26 '13 at 09:43
  • @MSalters The OP seems to be under the impression that only native integer types are easily stored and can be read very fast. There's no other good reason to insist on a "standard" type. – Jim Balter Aug 26 '13 at 10:00
  • The reason behind is, that I need to make it accessible to the scripting engine. Therefore, it needs to be compatible to a standard type. I could use a custom struct but I'd need to mirror that type to the Javascript context. Instead I'd like to just store it in a standard numeric Javascript variable. But I notices that this isn't easy since they are only in the range of ± 2^53 or so. – danijar Aug 26 '13 at 12:41

7 Answers7

90

Although GCC does provide __int128, it is supported only for targets (processors) which have an integer mode wide enough to hold 128 bits. On a given system, sizeof() intmax_t and uintmax_t determine the maximum value that the compiler and the platform support.

Onkar
  • 1,101
  • 8
  • 7
  • 12
    Upvoted because it points out an issue with the accepted answer. – CppNoob Feb 02 '16 at 11:36
  • 8
    This isn't quite correct: `__int128_t` is supported on x86-64 (but not i386). It's implemented in 64bit integer registers using addition-with-carry, and extended-precision code for shifts, multiplies, and so on. (The 128b SSE vector registers aren't useful for anything except boolean (AND/OR/XOR), because they can't do a single 128b add. SSE can do two 64b adds, or multiple smaller elements). – Peter Cordes Feb 14 '16 at 22:48
53

GCC and Clang support __int128

doron
  • 27,972
  • 12
  • 65
  • 103
  • 7
    only for 64-bit architectures, though. See https://gcc.gnu.org/onlinedocs/gcc/_005f_005fint128.html – user3342339 Feb 26 '16 at 19:41
  • 2
    Non 64-bit desktops are very rare and if your arduino would totally ever need 128bits to store a single value that's a different topic. @user3342339 –  Dec 05 '19 at 17:34
  • 2
    Is there a preprocessor define such as `HAVE_INT_128_T` or something similar we could check? – einpoklum Feb 10 '20 at 23:01
50

Checkout boost's implementation:

#include <boost/multiprecision/cpp_int.hpp>

using namespace boost::multiprecision;

int128_t v = 1;

This is better than strings and arrays, especially if you need to do arithmetic operations with it.

Craig McQueen
  • 41,871
  • 30
  • 130
  • 181
Patrik Beck
  • 2,455
  • 1
  • 19
  • 24
  • 16
    Boost also has a [uuid library](http://www.boost.org/doc/libs/1_54_0/libs/uuid/) btw. – Jesse Good Aug 26 '13 at 08:26
  • @JesseGood I may use that library, but I'd like store the ids in a standard type. – danijar Aug 26 '13 at 08:35
  • Regarding boost, you can also have a look at this post: http://stackoverflow.com/a/16218841/52568 – Kurt Pattyn Aug 26 '13 at 08:41
  • 5
    @danijar: What "standard"? `boost` uses `uint8_t data[16];` while `libuuid` is `unsigned char data[16];`, all libraries I know use arrays. – Jesse Good Aug 26 '13 at 08:43
  • @JesseGood Okay, I didn't knew that. Then this is fine. The only challenge might be to use it as key in an `std::unordered_map`. I am not sure if arrays can be used without providing my own hash function. – danijar Aug 26 '13 at 09:09
  • 1
    @danijar: That's not too difficult, [see the answer here](http://stackoverflow.com/questions/16471051/boostuuidsuuid-as-a-key-in-stdunordered-map). – Jesse Good Aug 26 '13 at 09:28
  • @JesseGood Curious as to why they would use `uint8_t`. There are only two operations (other than copy and assignment) which make sense on a UUID: comparison for equality, and conversion to a string. The larger the basic type, the faster the comparison (up to a point), and the string format of the UUID suggests `uint16_t`. So `uint16_t` or larger would seem to be in order. – James Kanze Aug 26 '13 at 10:15
  • @JamesKanze: I'm not 100 percent sure, but byte-ordering would be a factor since uuids are often used in network messaging. – Jesse Good Aug 26 '13 at 10:43
  • @JesseGood Good point. That probably explains it. (In all of the cases where I've used UUID, they've ended up in text files, so the byte order has been irrelevant.) – James Kanze Aug 26 '13 at 11:00
  • @JamesKanze: The type of storage should be negligible as UUIDs are fixed size and can therefor be compared via `std::memcmp`. (assuming that the byte-order of both UUIDs is equal [which it should be anyways]) – MFH Aug 26 '13 at 14:02
  • @MFH But `memcmp` will probably be less than optimal for the comparison. (Whether that's relevant or not is another question.) – James Kanze Aug 27 '13 at 12:01
  • @JamesKanze: I'd say that's a question of implementation. There is nothing preventing a 100% standard compatible version to reinterpret the pointers as `const unsigned long long` when `count == 2 * sizeof(unsigned long long)`. If it does that than it will actually be able to compare both UUIDs with 4 read operations and 2 compares. But even if it doesn't do that it will still very likely be extremely fast due to caching. – MFH Aug 27 '13 at 13:12
  • boost::multiprecision::int128_t seems to have performance issues compared against __int128_t. {code} #include "vlib/stopwatch.hpp" #include #include #include using namespace std; int main(int , char **argv) { size_t cnt = std::stoull(argv[1]); __int128_t counter = 0; for(size_t i = 0; i < cnt; ++i) { ++counter; counter <<= 2; } } {code} boost takes 20x the time on this code. – user1418199 Mar 04 '16 at 16:20
13

Your question has two parts.

1.128-bit integer. As suggested by @PatrikBeck boost::multiprecision is good way for really big integers.

2.Variable to store UUID / GUID / CLSID or whatever you call it. In this case boost::multiprecision is not a good idea. You need GUID structure which is designed for that purpose. As cross-platform tag added, you can simply copy that structure to your code and make it like:

struct GUID
{
    uint32_t Data1;
    uint16_t Data2;
    uint16_t Data3;
    uint8_t  Data4[8];
};

This format is defined by Microsoft because of some inner reasons, you can even simplify it to:

struct GUID
{
    uint8_t Data[16];
};

You will get better performance having simple structure rather than object that can handle bunch of different stuff. Anyway you don't need to do math with GUIDS, so you don't need any fancy object.

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
ST3
  • 8,826
  • 3
  • 68
  • 92
6

I would recommend using std::bitset<128> (you can always do something like using UUID = std::bitset<128>;). It will probably have a similar memory layout to the custom struct proposed in the other answers, but you won't need to define your own comparison operators, hash etc.

Mikhail Vasilyev
  • 503
  • 4
  • 11
4

There is no 128-bit integer in Visual-C++ because the Microsoft calling convention only allows returning of 2 32-bit values in the RAX:EAX pair. The presents a constant headache because when you multiply two integers together with the result is a two-word integer. Most load-and-store machines support working with two CPU word-sized integers but working with 4 requires software hack, so a 32-bit CPU cannot process 128-bit integers and 8-bit and 16-bit CPUs can't do 64-bit integers without a rather costly software hack. 64-bit CPUs can and regularly do work with 128-bit because if you multiply two 64-bit integers you get a 128-bit integer so GCC version 4.6 does support 128-bit integers. This presents a problem with writing portable code because you have to do an ugly hack where you return one 64-bit word in the return register and you pass the other in using a reference. For example, in order to print a floating-point number fast with Grisu we use 128-bit unsigned multiplication as follows:

#include <cstdint>
#if defined(_MSC_VER) && defined(_M_AMD64)
#define USING_VISUAL_CPP_X64 1
#include <intrin.h>
#include <intrin0.h>
#pragma intrinsic(_umul128)
#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
#define USING_GCC 1
#if defined(__x86_64__)
#define COMPILER_SUPPORTS_128_BIT_INTEGERS 1
#endif
#endif

#if USING_VISUAL_CPP_X64
    UI8 h;
    UI8 l = _umul128(f, rhs_f, &h);
    if (l & (UI8(1) << 63))  // rounding
      h++;
    return TBinary(h, e + rhs_e + 64);
#elif USING_GCC
    UIH p = static_cast<UIH>(f) * static_cast<UIH>(rhs_f);
    UI8 h = p >> 64;
    UI8 l = static_cast<UI8>(p);
    if (l & (UI8(1) << 63))  // rounding
      h++;
    return TBinary(h, e + rhs_e + 64);
#else
    const UI8 M32 = 0xFFFFFFFF;
    const UI8 a = f >> 32;
    const UI8 b = f & M32;
    const UI8 c = rhs_f >> 32;
    const UI8 d = rhs_f & M32;
    const UI8 ac = a * c;
    const UI8 bc = b * c;
    const UI8 ad = a * d;
    const UI8 bd = b * d;
    UI8 tmp = (bd >> 32) + (ad & M32) + (bc & M32);
    tmp += 1U << 31;  /// mult_round
    return TBinary(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e + rhs_e + 64);
#endif
  }
1

Use the TBigInteger template and set any bit range in the template array like this TBigInt<128,true> for being a signed 128 bit integer or TBigInt<128,false> for being an unsigned 128 bit integer.

Hope that helps maybe a late reply and someone else found this method already.

The TBigInt is a structure defined by Unreal Engine. It provides a multi-bit integer override.

Basic usage (as far as I can tell):

#include <Math/BigInt.h>

void foo() {
    TBigInt<128, true> signed128bInt = 0;
    TBigInt<128, false> unsigned128bInt = 0;
}
SimonC
  • 1,547
  • 1
  • 19
  • 43
Silanus
  • 11
  • 1