c语言64位乘法,关于c ++:获得64位整数乘法的高分

在C ++中,说:

uint64_t i;

uint64_t j;

那么i * j将产生uint64_t,其值为i和j之间的乘法的下半部分,即(i * j) mod 2^64。

现在,如果我想要乘法的较高部分怎么办? 我知道在使用32位整数时,存在一个汇编指令做类似的事情,但我对汇编并不熟悉,所以我希望得到帮助。

制作以下内容的最有效方法是:

uint64_t k = mulhi(i, j);

参考:blogs.msdn.com/b/oldnewthing/archive/2014/12/08/10578956.aspx

GCC为此目的uint128_t。 Visual Studio没有这样的选择。

@MooingDuck看起来我的环境下不存在uint128_t(我在osx下使用Xcode)。此外,这将明确计算该乘法的较高和较低部分,我想避免。

@NickyC感谢您的参考!正如我所说,我几乎没有组装经验。你能提供一个简单的示例代码来满足我的需求吗?对不起,我一定要一劳永逸地学习装配!

@MatteoMonti在没有下半部分的情况下计算较高部分是不可行的,因为从下部传递到较高部分。

@MatteoMonti这不是关于装配。我只是想向你展示数学。

@NickyC确实如此。谢谢。

如果性能不是一个大问题,请尝试使用任意长度的整数类来获得结果。

@NeilKirk的表现是我的主要关注点,实际上......

那么,如果我迁移到一个平台,我有uint128_t,这可能是最有效的方式来做我需要的?

如果表现是真正的问题。您需要学习足够的程序集以对此内联进行编码。在64位处理器上,(应该?)是将上下32位数相乘的指令。

stackoverflow.com/questions/25095741/ stackoverflow.com/questions/28766755/ stackoverflow.com/questions/87771/ stackoverflow.com/questions/28807341/

gcc中包含__int128以及包含Apple Clang的llvm。 stackoverflow.com/questions/13187629/

Java中的一些长乘法更高位?用C计算64x64 int产品的高64位合理的可移植方式从64x64位乘法获得前64位?装配中纯粹的高位乘法?

@L?uV?nhPhc谢谢,我想我此时只会使用128位乘法。这听起来比我自己可以实现的任何其他解决方案更具性能,因为我认为任何可能的优化必须已经由开发编译器的人实现。

@phuclv:这个问题与链接的问题不重复。另一个问题是关注32位乘法,而这个问题则集中在64位乘法上。当人们提出这个问题时,他们会按照链接(就像我做的那样)而不得不回到这个问题。我认为它应该重新打开(并且可能再次以更好的重复关闭)。

@Arnaud应该没什么区别。简单地将每个变量类型加倍,问题就解决了

但是,是的,可能另一个问题没有足够好的通用答案

@phuclv您不能轻易地将变量类型加倍。您需要128位整数类型。

@Arnaud不,你不需要仅仅采取64x64乘法的较高部分,例如只是在另一个问题中加宽汇编指令而你很好。你有没有看到我的其他相关问题?

@phuclv这是一个C ++问题,而不是汇编问题。当然,我可以在汇编中找到两个64位寄存器的解决方案。重点是要知道这是否可以在C ++中移植。如果你陷入可移植的C ++,32位问题有一个简单的答案(乘以两个std :: uint64_t)和64位问题是困难的(因为我们没有std :: uint128_t)

让我们在聊天中继续讨论。

更好的欺骗是在C中计算高64位的64x64 int产品 - 这个答案清楚地表明了如何为类似问题获得良好的结果。

如果您正在使用gcc并且您支持的128位数字(尝试使用__uint128_t)比执行128位乘法并提取高64位可能是获得结果的最有效方法。

如果您的编译器不支持128位数,那么Yakk的答案是正确的。但是,对于一般消费来说,它可能太短暂了。特别是,实际的实现必须小心溢出64位整数。

他提出的简单易用的解决方案是将a和b中的每一个分成2个32位数,然后使用64位乘法运算将这些32位数相乘。如果我们写:

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;

那很明显:

a = (a_hi << 32) + a_lo;

b = (b_hi << 32) + b_lo;

和:

a * b = ((a_hi << 32) + a_lo) * ((b_hi << 32) + b_lo)

= ((a_hi * b_hi) << 64) +

((a_hi * b_lo) << 32) +

((b_hi * a_lo) << 32) +

a_lo * b_lo

如果使用128位(或更高)算术执行计算。

但是这个问题需要我们使用64位算术执行所有的计算,所以我们不得不担心溢出。

由于a_hi,a_lo,b_hi和b_lo都是无符号32位数,因此它们的乘积将适合无符号的64位数而不会溢出。但是,上述计算的中间结果不会。

以下代码将实现mulhi(a,b),当必须以2 ^ 64模数执行数学化验时:

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;

return multhi;

正如Yakk指出的那样,如果你不介意在高64位中被+1关闭,你可以省略进位的计算。

不幸的是,目前的编译器没有优化@ craigster0的漂亮便携版本,所以如果你想利用64位CPU,你不能使用它,除非作为你没有#ifdef的目标的后备。 (我没有看到优化它的通用方法;您需要128位类型或内在类型。)

GNU C(gcc,clang或ICC)在大多数64位平台上都有unsigned __int128。 (或者在旧版本中,__uint128_t)。但是,GCC并未在32位平台上实现此类型。

这是让编译器发出64位全乘法指令并保持高半部分的简单有效方法。 (GCC知道转换为128位整数的uint64_t仍然将上半部分全部置零,所以你不能使用三个64位乘法得到128位乘法。)

MSVC还具有64位高半乘法的__umulh内在函数,但同样只能在64位平台上使用(特别是x86-64和AArch64。文档还提到了_umul128的IPF(IA-64)可用,但我没有安腾的MSVC。(可能无论如何都不相关。)

#define HAVE_FAST_mul64 1

#ifdef __SIZEOF_INT128__     // GNU C

static inline

uint64_t mulhi64(uint64_t a, uint64_t b) {

unsigned __int128 prod =  a * (unsigned __int128)b;

return prod >> 64;

}

#elif defined(_M_X64) || defined(_M_ARM64)     // MSVC

// MSVC for x86-64 or AArch64

// possibly also  || defined(_M_IA64) || defined(_WIN64)

// but the docs only guarantee x86-64!  Don't use *just* _WIN64; it doesn't include AArch64 Android / Linux

// https://docs.microsoft.com/en-gb/cpp/intrinsics/umulh

#include

#define mulhi64 __umulh

#elif defined(_M_IA64) // || defined(_M_ARM)       // MSVC again

// https://docs.microsoft.com/en-gb/cpp/intrinsics/umul128

// incorrectly say that _umul128 is available for ARM

// which would be weird because there's no single insn on AArch32

#include

static inline

uint64_t mulhi64(uint64_t a, uint64_t b) {

unsigned __int64 HighProduct;

(void)_umul128(a, b, &HighProduct);

return HighProduct;

}

#else

# undef HAVE_FAST_mul64

uint64_t mulhi64(uint64_t a, uint64_t b);  // non-inline prototype

// or you might want to define @craigster0's version here so it can inline.

#endif

对于x86-64,AArch64和PowerPC64(以及其他),这将编译为一个mul指令,以及一对mov来处理调用约定(在此内联后应该优化掉)。

来自Godbolt编译器资源管理器(x86-64,PowerPC64和AArch64的源代码为+ asm):

# x86-64 gcc7.3.  clang and ICC are the same.  (x86-64 System V calling convention)

# MSVC makes basically the same function, but with different regs for x64 __fastcall

mov     rax, rsi

mul     rdi              # RDX:RAX = RAX * RDI

mov     rax, rdx

ret

(或使用clang -march=haswell启用BMI2:mov rdx, rsi / mulx rax, rcx, rdi直接将高半部分放入RAX.gcc是哑的,仍然使用额外的mov。)

对于AArch64(gcc unsigned __int128或MSVC __umulh):

test_var:

umulh   x0, x0, x1

ret

使用2乘法器的编译时恒定功率,我们通常会获得预期的右移以获得一些高位。但gcc有趣地使用shld(参见Godbolt链接)。

不幸的是,当前的编译器并没有优化@ craigster0的便携版本。你得到8x shr r64,32,4x imul r64,r64和x86-64的一堆add / mov指令。即它编译为大量32x32 => 64位乘法和解包结果。因此,如果您想要利用64位CPU的东西,则需要一些#ifdef s。

在Intel CPU上,全乘mul 64指令是2 uop,但仍然只有3个周期延迟,与imul r64,r64相同,只产生64位结果。因此,基于http://agner.org/optimize的快速眼球猜测,__int128 /内在版本在现代x86-64上的延迟和吞吐量(对周围代码的影响)比便携版本便宜5到10倍。 /。

在上面的链接上查看Godbolt编译器资源管理器。

gcc在乘以16时会完全优化此函数,但是:您获得单个右移,比unsigned __int128乘法更有效。

长乘法应该是好的表现。

将a*b拆分为(hia+loa)*(hib+lob)。这给出了4个32位乘法加上一些移位。用64位进行,并手动进行进位,你将获得高分。

注意,高部分的近似可以用较少的乘法来完成 - 准确地在2 ^ 33左右,1乘法,并且在1乘3乘法。

我不认为有便携式替代品。

为什么不携带? 您甚至可以在C中进行任意精度数学运算而无需任何装配

@luru我的意思是快速便携式替代品。 这基本上是一个微小的最大尺寸的bignum。

这是我今晚提出的经过单元测试的版本,提供完整的128位产品。在检查时,它似乎比大多数其他在线解决方案(例如Botan库和其他答案)更简单,因为它利用了MIDDLE PART如何不溢出,如代码注释中所解释的那样。

对于上下文,我为这个github项目编写了它:https://github.com/catid/fp61

//------------------------------------------------------------------------------

// Portability Macros

// Compiler-specific force inline keyword

#ifdef _MSC_VER

# define FP61_FORCE_INLINE inline __forceinline

#else

# define FP61_FORCE_INLINE inline __attribute__((always_inline))

#endif

//------------------------------------------------------------------------------

// Portable 64x64->128 Multiply

// CAT_MUL128: r{hi,lo} = x * y

// Returns low part of product, and high part is set in r_hi

FP61_FORCE_INLINE uint64_t Emulate64x64to128(

uint64_t& r_hi,

const uint64_t x,

const uint64_t y)

{

const uint64_t x0 = (uint32_t)x, x1 = x >> 32;

const uint64_t y0 = (uint32_t)y, y1 = y >> 32;

const uint64_t p11 = x1 * y1, p01 = x0 * y1;

const uint64_t p10 = x1 * y0, p00 = x0 * y0;

/*

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

const uint64_t middle = p10 + (p00 >> 32) + (uint32_t)p01;

/*

Proof that 64-bit products can accumulate two more 32-bit values

without overflowing:

Max 32-bit value is 2^32 - 1.

PSum = (2^32-1) * (2^32-1) + (2^32-1) + (2^32-1)

= 2^64 - 2^32 - 2^32 + 1 + 2^32 - 1 + 2^32 - 1

= 2^64 - 1

Therefore it cannot overflow regardless of input.

*/

// 64-bit product + two 32-bit values

r_hi = p11 + (middle >> 32) + (p01 >> 32);

// Add LOW PART and lower half of MIDDLE PART

return (middle << 32) | (uint32_t)p00;

}

#if defined(_MSC_VER) && defined(_WIN64)

// Visual Studio 64-bit

# include

# pragma intrinsic(_umul128)

# define CAT_MUL128(r_hi, r_lo, x, y) \

r_lo = _umul128(x, y, &(r_hi));

#elif defined(__SIZEOF_INT128__)

// Compiler supporting 128-bit values (GCC/Clang)

# define CAT_MUL128(r_hi, r_lo, x, y)                   \

{                                                   \

unsigned __int128 w = (unsigned __int128)x * y; \

r_lo = (uint64_t)w;                             \

r_hi = (uint64_t)(w >> 64);                     \

}

#else

// Emulate 64x64->128-bit multiply with 64x64->64 operations

# define CAT_MUL128(r_hi, r_lo, x, y) \

r_lo = Emulate64x64to128(r_hi, x, y);

#endif // End CAT_MUL128

(您的注释在代码上方和代码下方之间交替显示。)

我将它移植到C#,它比我遇到的任何其他64x64功能更快!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值