将两个UInt32相乘以获得不扩展的UInt64

时间:2017-08-25 21:50:28

标签: delphi

对于我的BigIntegers,在PUREPASCAL实现中(即不允许汇编程序),我必须乘以两个UInt32才能获得UInt64结果。

通常的做法是扩展至少一个操作数,这样就得到64位乘法:

Res := UInt64(A) * B;

其中ResUInt64ABUInt32

但是,在Win32中,这会产生一个相当笨重的机器代码:

MulTest.dpr.431: Res := UInt64(A) * B;
004DB463 8B45F8           mov eax,[ebp-$08]  // load A 
004DB466 33D2             xor edx,edx        // make it UInt64
004DB468 52               push edx           // push A
004DB469 50               push eax
004DB46A 8B45FC           mov eax,[ebp-$04]  // load B
004DB46D 33D2             xor edx,edx        // make it UInt64 
004DB46F E87C0AF3FF       call @_llmul       // 64 bit multiplication
004DB474 8945E8           mov [ebp-$18],eax  // store 64 bit result
004DB477 8955EC           mov [ebp-$14],edx

现在,如果您这样做:

Res := A * B;
不幸的是,你获得了32位的中间结果(实际结果的前32位被简单地清零):

MulTest.dpr.435: Res := A * B;
004DB4BD 8B45FC           mov eax,[ebp-$04]
004DB4C0 F76DF8           imul dword ptr [ebp-$08]
004DB4C3 33D2             xor edx,edx              // zero out top 32 bits
004DB4C5 8945E8           mov [ebp-$18],eax
004DB4C8 8955EC           mov [ebp-$14],edx

现在,如果行xor edx,edx不存在,那么结果将正是我所需要的。这比使用UInt64演员的加宽版本快两倍(即花费不到一半的时间)。

问题:有没有人知道是否存在不丢弃64位结果的前32位的伪函数或技巧或演员?我知道如何在汇编程序中执行此操作,但这必须是PUREPASCAL(它也应该适用于其他平台)。

我设法通过访问32位未分配整数数组,在PUREPASCAL中更快地生成32位加法,这些整数组成BigInteger作为无符号16位整数数组并添加它们。所以我也尝试使用16位中间结果进行乘法运算:

// Too slow: in a test, 2973 ms for Mul32(A, B) vs 1432 ms for UInt64(A) * B.
function MulU32ToU64(L, R: UInt32): UInt64; inline;
var
  L0R0, L0R1, L1R0, L1R1, Sum: UInt32;
type
  TUInt64 = packed record
    case Byte of
      0: (L0, L1, L2, L3: UInt16);
      1: (I0, I1: UInt32);
  end;
  TUInt32 = packed record
    Lo, Hi: Word;
  end;
begin
  L0R0 := TUInt32(L).Lo * TUInt32(R).Lo;
  L0R1 := TUInt32(L).Lo * TUInt32(R).Hi;
  L1R0 := TUInt32(L).Hi * TUInt32(R).Lo;
  L1R1 := TUInt32(L).Hi * TUInt32(R).Hi;
  TUInt64(Result).L0 := TUInt32(L0R0).Lo;
  Sum := UInt32(TUInt32(L0R0).Hi) + TUInt32(L1R0).Lo + TUInt32(L0R1).Lo;
  TUInt64(Result).L1 := TUInt32(Sum).Lo;
  Sum := UInt32(TUInt32(Sum).Hi) + TUInt32(L1R0).Hi + TUInt32(L0R1).Hi + L1R1;
  TUInt64(Result).I1 := Sum;
end;

它给了我正确的结果,但是的结果是UInt64(A) * B的两倍多。这并不奇怪,因为它进行了4次UInt32乘法和大量添加,这使得它比使用System.__llmul的代码慢。

更新

正如@J ...指出的那样,Delphi通常使用IMUL,它进行有符号的乘法运算。所以乘以例如$00000002$FFFFFFFF会产生EAX = $FFFFFFFEEDX = $FFFFFFFF(换句话说,Int64的值为-2),而我需要{{} 1}}(相同),但EAX = $FFFFFFFE(一起EDX = $00000001,值UInt64)。因此,排除前32位是正确的,似乎没有办法强迫Delphi使用$00000001FFFFFFFE并保留结果的前32位。

2 个答案:

答案 0 :(得分:6)

MulTest.dpr.435: Res := A * B;
004DB4BD 8B45FC           mov eax,[ebp-$04]
004DB4C0 F76DF8           imul dword ptr [ebp-$08]
004DB4C3 33D2             xor edx,edx              // zero out top 32 bits
004DB4C5 8945E8           mov [ebp-$18],eax
004DB4C8 8955EC           mov [ebp-$14],edx
  

现在,如果xor edx,edx不存在,那么结果将是我所需要的。

不,它根本不是你想要的。这是一个有符号的乘法,如果你想要一个无符号的结果,结果是无意义的。制作A:=$FFFFFFFFB:=2 - imul的结果为EAX = FFFFFFFEEDX = FFFFFFFF。即使使用两个无符号操作数,也会发出此操作码。您需要mul指令,而不是imul。我不认为delphi编译器会从纯粹的pascal中发出mul。来自the documentation on *(强调我的)

  

无论x和y的类型如何,x / y的值都是Extended类型。对于其他算术运算符,只要至少一个操作数是实数,结果就是Extended类型;否则,当至少一个操作数是Int64类型时,结果是Int64类型; 否则,结果为Integer类型

整数 - 已签名。鉴于这对于架构的特性有多依赖,并且考虑到delphi编译器的不足,我认为这里唯一的高性能解决方案将是依赖于目标的汇编。

function UMul3264(x, y : UInt32) : UInt64;
asm
  mul eax, edx
end;

答案 1 :(得分:0)

有一个 Windows 宏 UInt32x32To64(a, b) 可以将两个无符号 32 位值相乘并得到 64 位结果。

如果您需要纯 pascal,则必须将两个 32 位无符号值分配给 64 位无符号值,然后将它们相乘。

function UInt32x32To64(x, y: UInt32): UInt64;
var
  xl, yl: UInt64;
begin
  xl := x;
  yl := y;
  Result := xl * yl;
end;

这是检查此函数的代码示例。这段代码也有一个汇编函数只是为了比较。您不需要它,因为您有 PurePascal,但它的实现非常有效 - 只需一个 mul 指令。它是 mul 的一种特殊形式,它只接受一个参数,另一个来自 eax 寄存器。生成的 64 位值存储在 edx:eax 中。因此它的实现比将 32 位值转换为 64 位然后将它们相乘更有效,因为 Delphi 从 System.pas 调用 __llmul,它执行 3 个 mul 指令,每个指令成本很高。

program TestMultiply;
{$APPTYPE CONSOLE}

function UInt32x32To64_asm(x, y: UInt32): UInt64; assembler;
asm
  {$IFDEF WIN32}
  mul edx
  {$ELSE}
  mov eax, ecx
  mul edx
  shl rdx, 32
  or  rax, rdx
  {$ENDIF}
end;

function UInt32x32To64_purepascal(x, y: UInt32): UInt64;
var
  xl, yl: UInt64;
begin
  xl := x;
  yl := y;
  Result := xl * yl;
end;

var
  aa, bb: UInt32;
  cc, cc_test: UInt64;
begin
  aa := 2147483647;
  bb := 2148736590;
  cc_test := 4614376688735543730;
  cc := UInt32x32To64_asm(aa, bb);
  if cc <> cc_test then
    WriteLn('Error')
  else
    WriteLn('OK');
  cc := UInt32x32To64_purepascal(aa, bb);
  if cc <> cc_test then
    WriteLn('Error')
  else
    WriteLn('OK');

end.

这是要检查的 Python3 代码:

aa = 2147483647
bb = 2148736590
cc = aa * bb
print(cc)
print("Error") if cc != 4614376688735543730 else print("OK!")