对于我的 BigInteger 代码,对于非常大的 BigInteger,输出结果很慢。所以现在我使用递归分治算法,它仍然需要 2'30" 才能将当前最大的已知素数转换为超过 2200 万位的十进制字符串(但只需 135 毫秒即可将其转换为十六进制字符串) .
我仍然想减少时间,所以我需要一个可以非常快地将 NativeUInt(即 32 位平台上的 UInt32,64 位平台上的 UInt64)除以 100 的例程。所以我用常数乘法。这在 32 位代码中可以正常工作,但我不能 100% 确定 64 位。
所以我的问题是:有没有办法检查无符号 64 位值乘以常数的结果的可靠性?我通过简单地尝试 UInt32 (0..$FFFFFFFF) 的所有值来检查 32 位值。这花了大约。3分钟。检查所有 UInt64 所花费的时间比我有生之年要长得多。有没有办法检查所使用的参数(常量、后移)是否可靠?
我注意到,如果选择的参数错误(但接近) ,DivMod100()
总是会失败。$4000004B
是否有特殊值或范围来检查 64 位,所以我不必检查所有值?
我当前的代码:
const
{$IF DEFINED(WIN32)}
// Checked
Div100Const = UInt32(UInt64($1FFFFFFFFF) div 100 + 1);
Div100PostShift = 5;
{$ELSEIF DEFINED(WIN64)}
// Unchecked!!
Div100Const = $A3D70A3D70A3D71;
// UInt64(UInt128($3 FFFF FFFF FFFF FFFF) div 100 + 1);
// UInt128 is fictive type.
Div100PostShift = 2;
{$IFEND}
// Calculates X div 100 using multiplication by a constant, taking the
// high part of the 64 bit (or 128 bit) result and shifting
// right. The remainder is calculated as X - quotient * 100;
// This was tested to work safely and quickly for all values of UInt32.
function DivMod100(var X: NativeUInt): NativeUInt;
{$IFDEF WIN32}
asm
// EAX = address of X, X is UInt32 here.
PUSH EBX
MOV EDX,Div100Const
MOV ECX,EAX
MOV EAX,[ECX]
MOV EBX,EAX
MUL EDX
SHR EDX,Div100PostShift
MOV [ECX],EDX // Quotient
// Slightly faster than MUL
LEA EDX,[EDX + 4*EDX] // EDX := EDX * 5;
LEA EDX,[EDX + 4*EDX] // EDX := EDX * 5;
SHL EDX,2 // EDX := EDX * 4; 5*5*4 = 100.
MOV EAX,EBX
SUB EAX,EDX // Remainder
POP EBX
end;
{$ELSE WIN64}
asm
.NOFRAME
// RCX is address of X, X is UInt64 here.
MOV RAX,[RCX]
MOV R8,RAX
XOR RDX,RDX
MOV R9,Div100Const
MUL R9
SHR RDX,Div100PostShift
MOV [RCX],RDX // Quotient
// Faster than LEA and SHL
MOV RAX,RDX
MOV R9D,100
MUL R9
SUB R8,RAX
MOV RAX,R8 // Remainder
end;
{$ENDIF WIN32}