1

I am trying to multiply two uint64_ts and store the result to uint64_t. I found an existing answer on Stackoverflow which splits the inputs in to their four uint32_ts and joins the result later:

https://stackoverflow.com/a/28904636/1107474

I have created a full example using the code and pasted it below.

However, for 37 x 5 I am getting the result 0 instead of 185?

#include <iostream>

int main()
{
    uint64_t a = 37;   // Input 1
    uint64_t b = 5;    // Input 2
    
    uint64_t    a_lo = (uint32_t)a;
    uint64_t    a_hi = a >> 32;
    uint64_t    b_lo = (uint32_t)b;
    uint64_t    b_hi = b >> 32;
    
    uint64_t    a_x_b_hi =  a_hi * b_hi;
    uint64_t    a_x_b_mid = a_hi * b_lo;
    uint64_t    b_x_a_mid = b_hi * a_lo;
    uint64_t    a_x_b_lo =  a_lo * b_lo;
    
    uint64_t    carry_bit = ((uint64_t)(uint32_t)a_x_b_mid +
                             (uint64_t)(uint32_t)b_x_a_mid +
                             (a_x_b_lo >> 32) ) >> 32;
    
    uint64_t    multhi = a_x_b_hi +
                         (a_x_b_mid >> 32) + (b_x_a_mid >> 32) +
                         carry_bit;
                                      
    std::cout << multhi << std::endl;   // Outputs 0 instead of 185?
}
Jason
  • 36,170
  • 5
  • 26
  • 60
intrigued_66
  • 16,082
  • 51
  • 118
  • 189
  • Why isn't `a_x_b_lo` one of the items in `multhi`? – Eljay Dec 11 '22 at 23:46
  • @Eljay Related to your question, should `a_x_b_hi` be shifted because it's the high? – intrigued_66 Dec 12 '22 at 00:03
  • 1
    @Eljay: the `lo x lo` product only affects the high half of the full result via the carry-out from adding its high half to the low halves of the cross products. That's what `carry_bit` is doing. The answer they linked isn't trying to get the whole product, just the high half. – Peter Cordes Dec 12 '22 at 00:36
  • @PeterCordes • Ahh, that makes sense. Lack of a `multlo` (in hindsight, which I didn't notice at the time) and use of `uint64_t` and doing piecewise `uint32_t` operations confused me. – Eljay Dec 12 '22 at 12:33

2 Answers2

1

I'm merging your code with another answer in the original link.

#include <iostream>

int main()
{
    uint64_t a = 37;   // Input 1
    uint64_t b = 5;    // Input 2
    
    uint64_t    a_lo = (uint32_t)a;
    uint64_t    a_hi = a >> 32;
    uint64_t    b_lo = (uint32_t)b;
    uint64_t    b_hi = b >> 32;
    
    uint64_t    a_x_b_hi =  a_hi * b_hi;
    uint64_t    a_x_b_mid = a_hi * b_lo;
    uint64_t    b_x_a_mid = b_hi * a_lo;
    uint64_t    a_x_b_lo =  a_lo * b_lo;
    
                                     
    /*
        This is implementing schoolbook multiplication:

                x1 x0
        X       y1 y0
        -------------
                   00  LOW PART
        -------------
                00
             10 10     MIDDLE PART
        +       01
        -------------
             01 
        + 11 11        HIGH PART
        -------------
    */

    // 64-bit product + two 32-bit values
    uint64_t middle = a_x_b_mid + (a_x_b_lo >> 32) + uint32_t(b_x_a_mid);

    // 64-bit product + two 32-bit values
    uint64_t carry = a_x_b_hi + (middle >> 32) + (b_x_a_mid >> 32);

    // Add LOW PART and lower half of MIDDLE PART
    uint64_t result = (middle << 32) | uint32_t(a_x_b_lo);

    std::cout << result << std::endl;
    std::cout << carry << std::endl;
}

This results in

Program stdout
185
0

Godbolt link: https://godbolt.org/z/97xhMvY53

Or you could use __uint128_t which is non-standard but widely available.

static inline void mul64(uint64_t a, uint64_t b, uint64_t& result, uint64_t& carry) {
    __uint128_t va(a);
    __uint128_t vb(b);
    __uint128_t vr = va * vb;
    result = uint64_t(vr);
    carry = uint64_t(vr >> 64);
}

Something Something
  • 3,999
  • 1
  • 6
  • 21
  • 1
    Thank you for your answer. I'm trying to multiply `371.1608054189` (mantissa `3711608054189`) by `0.002699633523001229` (mantissa `2699633523001229`) which should give `1.0019981527329986544145598281`. However, when I put both these mantissas in the 64-bit code I get result with mantissa `180588304518940489` – intrigued_66 Dec 12 '22 at 00:22
  • 1
    The reason i'm avoiding 128 bits for now is because I'll face a similar problem with 128 bits- division can create very large mantissas. So I wanted to understand the general principle of splitting in half to perform multiplications. – intrigued_66 Dec 12 '22 at 00:29
  • 1
    In Godbolt if I shorten each mantissa to `3711608054` and `2699633523` I get `10019981526815194242`. But I don't understand why I can't use my original mantissas? – intrigued_66 Dec 12 '22 at 00:35
  • 1
    I'll accept this answer because you did answer my original Q and create a new question. – intrigued_66 Dec 12 '22 at 00:37
  • 1
    @intrigued_66: for normalized floats, the mantissa has a leading `1` bit, so the product is always nearly full-range. Your 37 x 5 product would only happen with tiny subnormal floats. (Where the product would indeed underflow to zero, because you only get subnormals at the limits of the exponent range.) I'm surprised this answer doesn't explicitly say that it's normal for the high-half of the product of two small numbers to be zero, and maybe you're mixing up the high half with the whole thing, since this is integer math. – Peter Cordes Dec 12 '22 at 00:39
  • 1
    @PeterCordes I don't fully understand if I'm honest. I'm just creating a new question with this answer and the two actual numbers/mantissas i'm trying to multiply. – intrigued_66 Dec 12 '22 at 00:40
  • 2
    @intrigued_66: In the title of this question, you said you wanted to multiply two integers. But the code you found on that other Q&A isn't trying to do that, it's only trying to get the high half of the full product. That's why it's zero. In a floating-point multiply, you have two n-bit mantissas (usually with their leading bits set), so the high half of the 2n-bit product will have n significant bits (more or less; maybe actually one place to the right IIRC). But you're using integers where `a>>63` isn't `1`, so the only significant bits are in the low half. – Peter Cordes Dec 12 '22 at 00:44
  • @PeterCordes Thank you for rewording, now I understand. – intrigued_66 Dec 12 '22 at 00:53
1

In the title of this question, you said you wanted to multiply two integers. But the code you found on that other Q&A (Getting the high part of 64 bit integer multiplication) isn't trying to do that, it's only trying to get the high half of the full product. For a 64x64 => 128-bit product, the high half is product >> 64.

  • 37 x 5 = 185

  • 185 >> 64 = 0

It's correctly emulating multihi = (37 * (unsigned __int128)5) >> 64, and you're forgetting about the >>64 part.

__int128 is a GNU C extension; it's much more efficient than emulating it manually with pure ISO C, but only supported on 64-bit targets by current compilers. See my answer on the same question. (ISO C23 is expected to have _BitInt(128) or whatever width you specify.)


In comments you were talking about floating-point mantissas. In an FP multiply, you have two n-bit mantissas (usually with their leading bits set), so the high half of the 2n-bit product will have n significant bits (more or less; maybe actually one place to the right IIRC).

Something like 37 x 5 would only happen with tiny subnormal floats, where the product would indeed underflow to zero. But in that case, it would be because you only get subnormals at the limits of the exponent range, and (37 * 2^-1022) * (5 * 2^-1022) would be 186 * 2^-2044, an exponent way too small to be represented in an FP format like IEEE binary64 aka double where -1022 was the minimum exponent.

You're using integers where a>>63 isn't 1, in fact they're both less than 2^32 so there are no significant bits outside the low 64 bits of the full 128-bit product.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847