我通过告诉编译器不使用AnyCpu设置,使用AProgrammer's C code,将时间降至约250毫秒。
在发布模式下,PRNG大约需要5毫秒(我有点怀疑;当我尝试运行PRNG时,我认为它被优化掉了),总时间降至约77毫秒。
我仍然不确定它是如何工作的,但链接的答案说该代码对于十进制支持有一些冗余操作。如果我知道足够的工作原理,我认为可以通过优化掉十进制支持来进一步减少时间。
Int64(有符号)速度稍慢(发布版78 vs 77毫秒,调试版慢约20毫秒),但基本上相同的速度。如果min=Int64.MinValue和max=Int64.MaxValue,则会失败并每次返回min,但对我能想到的其他组合都有效。
有符号数学对于直接缩放不太有用。我只是做了一些适用于我的用例的东西。因此,我进行了一些转换,似乎适用于一般的有符号情况,但可能还可以进行一些优化。
无符号缩放算法,转换为C#。
/// <summary>
/// Returns an accurate, 64-bit result from value * multiplier / divisor without overflow.
/// From https://dev59.com/lWoy5IYBdhLWcg3wKq4S#8757419
/// </summary>
/// <param name="value">The starting value.</param>
/// <param name="multiplier">The number to multiply by.</param>
/// <param name="divisor">The number to divide by.</param>
/// <returns>The result of value * multiplier / divisor.</returns>
private UInt64 MulDiv64U(UInt64 value, UInt64 multiplier, UInt64 divisor)
{
UInt64 baseVal = 1UL << 32;
UInt64 maxdiv = (baseVal - 1) * baseVal + (baseVal - 1);
// First get the easy thing
UInt64 res = (value / divisor) * multiplier + (value % divisor) * (multiplier / divisor);
value %= divisor;
multiplier %= divisor;
// Are we done?
if (value == 0 || multiplier == 0)
return res;
// Is it easy to compute what remain to be added?
if (divisor < baseVal)
return res + (value * multiplier / divisor);
// Now 0 < a < c, 0 < b < c, c >= 1ULL
// Normalize
UInt64 norm = maxdiv / divisor;
divisor *= norm;
value *= norm;
// split into 2 digits
UInt64 ah = value / baseVal, al = value % baseVal;
UInt64 bh = multiplier / baseVal, bl = multiplier % baseVal;
UInt64 ch = divisor / baseVal, cl = divisor % baseVal;
// compute the product
UInt64 p0 = al * bl;
UInt64 p1 = p0 / baseVal + al * bh;
p0 %= baseVal;
UInt64 p2 = p1 / baseVal + ah * bh;
p1 = (p1 % baseVal) + ah * bl;
p2 += p1 / baseVal;
p1 %= baseVal;
// p2 holds 2 digits, p1 and p0 one
// first digit is easy, not null only in case of overflow
UInt64 q2 = p2 / divisor;
p2 = p2 % divisor;
// second digit, estimate
UInt64 q1 = p2 / ch;
// and now adjust
UInt64 rhat = p2 % ch;
// the loop can be unrolled, it will be executed at most twice for
// even baseVals -- three times for odd one -- due to the normalisation above
while (q1 >= baseVal || (rhat < baseVal && q1 * cl > rhat * baseVal + p1))
{
q1--;
rhat += ch;
}
// subtract
p1 = ((p2 % baseVal) * baseVal + p1) - q1 * cl;
p2 = (p2 / baseVal * baseVal + p1 / baseVal) - q1 * ch;
p1 = p1 % baseVal + (p2 % baseVal) * baseVal;
// now p1 hold 2 digits, p0 one and p2 is to be ignored
UInt64 q0 = p1 / ch;
rhat = p1 % ch;
while (q0 >= baseVal || (rhat < baseVal && q0 * cl > rhat * baseVal + p0))
{
q0--;
rhat += ch;
}
// we don't need to do the subtraction (needed only to get the remainder,
// in which case we have to divide it by norm)
return res + q0 + q1 * baseVal; // + q2 *baseVal*baseVal
}
MulDiv64使用无符号版本进行有符号转换。在我的用例中速度较慢(调试时290毫秒对260毫秒,发布时95毫秒对81毫秒),但适用于一般情况。不适用于Int64.MinValue(引发异常:“否定补码数的最小值是无效的。”)。
public static Int64 MulDiv64(Int64 value, Int64 multiplier, Int64 divisor)
{
// Get the signs then convert to positive values.
bool isPositive = true;
if (value < 0) isPositive = !isPositive;
UInt64 val = (UInt64)Math.Abs(value);
if (multiplier < 0) isPositive = !isPositive;
UInt64 mult = (UInt64)Math.Abs(multiplier);
if (divisor < 0) isPositive = !isPositive;
UInt64 div = (UInt64)Math.Abs(divisor);
// Scaledown.
UInt64 scaledVal = MulDiv64U(val, mult, div);
// Convert to signed Int64.
Int64 result = (Int64)scaledVal;
if (!isPositive) result *= -1;
// Finished.
return result;
}
GetRangeU 函数返回一个介于最小值和最大值之间(包括最小值和最大值)的无符号 UInt64。缩放直接从早期函数进行。
/// <summary>
/// Returns a random unsigned integer between Min and Max, inclusive.
/// </summary>
/// <param name="min">The minimum value that may be returned.</param>
/// <param name="max">The maximum value that may be returned.</param>
/// <returns>The random value selected by the Fates for your application's immediate needs. Or their fickle whims.</returns>
public UInt64 GetRangeU(UInt64 min, UInt64 max)
{
// Swap inputs if they're in the wrong order.
if (min > max)
{
UInt64 Temp = min;
min = max;
max = Temp;
}
// Get a random integer.
UInt64 randInt = NextUInt64();
// Fraction randInt/MaxValue needs to be strictly less than 1.
if (randInt == UInt64.MaxValue) randInt = 0;
// Get the difference between min and max values.
UInt64 diff = max - min + 1;
// Scale randInt from the range 0, maxInt to the range 0, diff.
randInt = MulDiv64U(diff, randInt, UInt64.MaxValue);
// Add the minimum value and return the result.
return randInt;// randInt + min;
}
GetRange 函数返回一个在最小值和最大值之间的有符号 Int64。虽然不容易转换为一般缩放,但在这种情况下比上述方法更快。
/// <summary>
/// Returns a random signed integer between Min and Max, inclusive.
/// Returns min if min is Int64.MinValue and max is Int64.MaxValue.
/// </summary>
/// <param name="min">The minimum value that may be returned.</param>
/// <param name="max">The maximum value that may be returned.</param>
/// <returns>The random value selected.</returns>
public Int64 GetRange(Int64 min, Int64 max)
{
// Swap inputs if they're in the wrong order.
if (min > max)
{
Int64 Temp = min;
min = max;
max = Temp;
}
// Get a random integer.
UInt64 randInt = NextUInt64();
// Fraction randInt/MaxValue needs to be strictly less than 1.
if (randInt == UInt64.MaxValue) randInt = 0;
// Get the difference between min and max values.
UInt64 diff = (UInt64)(max - min) + 1;
// Scale randInt from the range 0, maxInt to the range 0, diff.
randInt = MulDiv64U(diff, randInt, UInt64.MaxValue);
// Convert to signed Int64.
UInt64 randRem = randInt % 2;
randInt /= 2;
Int64 result = min + (Int64)randInt + (Int64)randInt + (Int64)randRem;
// Finished.
return result;
}