0

I wrote my own Decimal class, which uses an int64_t mantissa. I believe this means it can represent 18 digits (would be 19 if unsigned).

I need to represent two ranges of number:

  • Type A as large as 99,999,999
  • Type B as small as 1e-9.

Until now, this was fine as these numbers were only added/subtracted. However, I now need to multiply and divide by each other.

I'm concerned I'm close to running out of digits.

  1. Should I use 128 bits instead?
  2. Are there any easy/simple 128 bit C++ decimal libraries?
  3. Could I simply replace my int64_t with __m128i?

I am not writing public/library code and my architecture will always be Linux x86. I'm currently using GCC but could switch to Clang.

intrigued_66
  • 16,082
  • 51
  • 118
  • 189
  • Your question is unclear. What is the range of products and quotients (type C and D ?) that you want to support ? And why would you compute quantities with 38 digits of accuracy ? –  Nov 05 '22 at 16:44
  • 2
    __m128i is *not* a 128 bits signed integer type ! –  Nov 05 '22 at 16:45
  • @YvesDaoust It's not that I want to support 38 digits, I'm concerned I'm close to exceeding 18 digits. – intrigued_66 Nov 05 '22 at 16:54
  • @YvesDaoust It's not an integer type, but couldn't I treat it as 128 consecutive bits? – intrigued_66 Nov 05 '22 at 16:56
  • Your description implies 38 digits. (And you did not answer my first question.) –  Nov 05 '22 at 17:05
  • I've stated the max and the min numbers....... wouldn't the largest quotient be max divided by min? – intrigued_66 Nov 05 '22 at 17:11
  • 1
    mezamorphic, "Until now, this was fine as these numbers were only added/subtracted." --> Posting that code would help understand the details of what you have successfully done. Without your code that attempts the "multiply and divide", it remains unclear what specific trouble you are having. "I'm concerned I'm close to running out of digits." is insufficient to explain the problem. Post your code to improve the question. – chux - Reinstate Monica Nov 05 '22 at 19:09
  • What about the smallest ? (You still didn't answer. Bye) –  Nov 05 '22 at 20:03
  • 2
    @mezamorphic: For scaled point arithmetic there's an unavoidable compromise between underlying size, range and precision, and every arithmetic operation changes that compromise. A library that only supports one format (e.g. "32 bits before the decimal point, 32 bits after the decimal point") cannot cope with the continually changing "best compromise". You should use a 128-bit integer type (e.g. the `__int128` extension in GCC or Clang maybe) when a specific expression requires it (possibly only for 2 operations in the middle of an expression that uses 64 bit integers everywhere else). – Brendan Nov 05 '22 at 21:19
  • 1
    You *can* treat `__m128i` as 128 consecutive bits, but only with the right intrinsics to extract parts of it. (Or maybe `memcpy` to copy bytes in and out of it). And that won't compile efficiently. If you mean with things like `v <<1 1`, no, that won't compile in MSVC, and in compilers that define `__m128i` in terms of GNU C extensions, it will be two right-shifts of the two (signed) `long long` halves separately. (Like a `psllq` instruction. And a right shift won't compile to a single instruction without AVX-512 for `vpsraq`; SSE/AVX2 only have arithmetic right shift for 8/16/32-bit) – Peter Cordes Nov 05 '22 at 22:37
  • 2
    https://godbolt.org/z/f7nfz1onh shows how << and >> compile for `__m128i`. It's not a scalar type. For a useful type (using GNU C extensions) see [Is there a 128 bit integer in gcc?](https://stackoverflow.com/q/16088282) and [Computing high 64 bits of a 64x64 int product in C](https://stackoverflow.com/q/1541426) – Peter Cordes Nov 05 '22 at 22:41
  • You say you need 18 decimal digits worth of precision. Now, the base-two logarithm of 1E18 is about 59.8, so 64 bits should be plenty. But, of course, multiplication is always hard, especially in fixed-point, because the result often overflows; in general an N-bit number times an N-bit number yields a result with 2×N bits. What do you want do do in the case of overflow? – Steve Summit Nov 06 '22 at 13:20
  • 1
    One good technique is to split both of your numbers into high and low halves, then perform four multiplications each of N/2 × N/2 bits, then combine the results. But you still have to decide what to do in the case of overflow. – Steve Summit Nov 06 '22 at 13:21
  • Thank you @PeterCordes Steve and Brendan – intrigued_66 Dec 06 '22 at 19:32
  • @PeterCordes I've discovered the Boost multiprecision library and the whole bundle of GMP, MPFR etc. I'm curious why you didn't mention them? Is it because I stated "fixed" and technically they are still floating point? – intrigued_66 Dec 07 '22 at 11:27
  • You said you wanted a 128-bit integer type, which GCC / clang support natively on 128-bit targets (`__int128`), so that's faster than any library, so that's certainly what I thought of first. I probably didn't take in the details of your question that you would be using the 128-bit integer as part of a wider type. And/or, I didn't know what library support existed for base-10 types. If extended-precision binary FP works, that can also be pretty high performance, like double-double on modern CPUs with FMA. – Peter Cordes Dec 07 '22 at 14:31
  • @PeterCordes I probably didn't word it clearly..... I'm after a 128-bit fixed decimal library. I can see your answer was pointing me towards writing my own using a 128-bit integer but I think it's probably best I use a library. So GMP seems to be the de-facto best for fast and accurate? – intrigued_66 Dec 07 '22 at 17:50
  • I haven't looked into GMP recently except for it's lowest-level integer functions, which are of course binary integers. (Which you can use for fixed-point if you want.) Mostly GMP is good for arbitrary precision, so its functions have loops that add overhead if you actually only have two 64-bit limbs. – Peter Cordes Dec 07 '22 at 17:53
  • @PeterCordes short of writing my own, is there an alternative library you would recommend? I'm not calculating millions of calculations, just a handful but i'd like to be as fast as possible. – intrigued_66 Dec 07 '22 at 18:39
  • If Boost has one, it might be good, hopefully taking advantage of whatever native compiler support exists. If your compiler supports a `_Decimal128` type, that would be decimal FP (proposed for C23; for C++ there was a `std::decimal::decimal128` proposal, see https://github.com/GaryHughes/stddecimal). So maybe slower than fixed-point since most hardware doesn't have decimal FP. – Peter Cordes Dec 07 '22 at 18:44
  • 1
    @PeterCordes I'm in that awkward window where 64 bits isnt enough but libraries such as GMP etc are too slow for 128 bits. I think I will use GCC's unsigned 128 bit integer and write my own class. I'll copy an implementation for the divide. – intrigued_66 Dec 08 '22 at 02:09

0 Answers0