I want to factor random values of n
that range from uint.MinValue
to uint.MaxValue
as efficiently as possible. It is my understanding that wheel factorization is about as good as it gets and this is my attempt at a 2, 3, 5
implementation.
public static IEnumerable<T> EnumeratePrimeFactors<T>(this T value) where T : IBinaryInteger<T>, IUnsignedNumber<T> {
if (BinaryIntegerConstants<T>.Four > value) { yield break; }
if (BinaryIntegerConstants<T>.Five == value) { yield break; }
if (BinaryIntegerConstants<T>.Seven == value) { yield break; }
if (BinaryIntegerConstants<T>.Eleven == value) { yield break; }
if (BinaryIntegerConstants<T>.Thirteen == value) { yield break; }
var index = value;
while (T.Zero == (index & T.One)/* enumerate factors of 2 */) {
yield return BinaryIntegerConstants<T>.Two;
index >>= 1;
}
while (T.Zero == (index % BinaryIntegerConstants<T>.Three)) { // enumerate factors of 3
yield return BinaryIntegerConstants<T>.Three;
index /= BinaryIntegerConstants<T>.Three;
}
while (T.Zero == (index % BinaryIntegerConstants<T>.Five)/* enumerate factors of 5 */) {
yield return BinaryIntegerConstants<T>.Five;
index /= BinaryIntegerConstants<T>.Five;
}
while (T.Zero == (index % BinaryIntegerConstants<T>.Seven)/* enumerate factors of 7 */) {
yield return BinaryIntegerConstants<T>.Seven;
index /= BinaryIntegerConstants<T>.Seven;
}
while (T.Zero == (index % BinaryIntegerConstants<T>.Eleven)/* enumerate factors of 11 */) {
yield return BinaryIntegerConstants<T>.Eleven;
index /= BinaryIntegerConstants<T>.Eleven;
}
while (T.Zero == (index % BinaryIntegerConstants<T>.Thirteen)/* enumerate factors of 13 */) {
yield return BinaryIntegerConstants<T>.Thirteen;
index /= BinaryIntegerConstants<T>.Thirteen;
}
var factor = BinaryIntegerConstants<T>.Seventeen;
var limit = index.SquareRoot();
if (factor <= limit) {
do {
while (T.Zero == (index % factor)/* enumerate factors of (30k - 13) */) {
yield return factor;
index /= factor;
}
factor += BinaryIntegerConstants<T>.Two;
while (T.Zero == (index % factor)/* enumerate factors of (30k - 11) */) {
yield return factor;
index /= factor;
}
factor += BinaryIntegerConstants<T>.Four;
while (T.Zero == (index % factor)/* enumerate factors of (30k - 7) */) {
yield return factor;
index /= factor;
}
factor += BinaryIntegerConstants<T>.Six;
while (T.Zero == (index % factor)/* enumerate factors of (30k - 1) */) {
yield return factor;
index /= factor;
}
factor += BinaryIntegerConstants<T>.Two;
while (T.Zero == (index % factor)/* enumerate factors of (30k + 1) */) {
yield return factor;
index /= factor;
}
factor += BinaryIntegerConstants<T>.Six;
while (T.Zero == (index % factor)/* enumerate factors of (30k + 7) */) {
yield return factor;
index /= factor;
}
factor += BinaryIntegerConstants<T>.Four;
while (T.Zero == (index % factor)/* enumerate factors of (30k + 11) */) {
yield return factor;
index /= factor;
}
factor += BinaryIntegerConstants<T>.Two;
while (T.Zero == (index % factor)/* enumerate factors of (30k + 13) */) {
yield return factor;
index /= factor;
}
factor += BinaryIntegerConstants<T>.Four;
limit = index.SquareRoot();
} while (factor <= limit);
}
if ((index != T.One) && (index != value)) {
yield return index;
}
}
Helpers:
public static class BinaryIntegerConstants<T> where T : IBinaryInteger<T>
{
public static T Eleven { get; }
public static T Five { get; }
public static T Four { get; }
public static T Seven { get; }
public static T Seventeen { get; }
public static T Six { get; }
public static int Size { get; }
public static T Thirteen { get; }
public static T Three { get; }
public static T Two { get; }
static BinaryIntegerConstants() {
var size = int.CreateChecked(value: T.PopCount(value: T.AllBitsSet));
Eleven = T.CreateTruncating(value: 11U);
Five = T.CreateTruncating(value: 5U);
Four = T.CreateTruncating(value: 4U);
Six = T.CreateTruncating(value: 6U);
Seven = T.CreateTruncating(value: 7U);
Seventeen = T.CreateTruncating(value: 17U);
Size = int.CreateChecked(value: size);
Thirteen = T.CreateTruncating(value: 13U);
Three = T.CreateTruncating(value: 3U);
Two = T.CreateTruncating(value: 2U);
}
}
public static class BinaryIntegerFunctions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static T As<T>(this bool value) where T : IBinaryInteger<T> =>
T.CreateTruncating(value: Unsafe.As<bool, byte>(source: ref value));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static T IsGreaterThan<T>(this T value, T other) where T : IBinaryInteger<T> =>
(value > other).As<T>();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T MostSignificantBit<T>(this T value) where T : IBinaryInteger<T> =>
(T.CreateTruncating(value: BinaryIntegerConstants<T>.Size) - T.LeadingZeroCount(value: value));
public static T SquareRoot<T>(this T value) where T : IBinaryInteger<T>, IUnsignedNumber<T> {
return BinaryIntegerConstants<T>.Size switch {
#if !FORCE_SOFTWARE_SQRT
8 => T.CreateTruncating(value: ((uint)MathF.Sqrt(x: uint.CreateTruncating(value: value)))),
16 => T.CreateTruncating(value: ((uint)MathF.Sqrt(x: uint.CreateTruncating(value: value)))),
32 => T.CreateTruncating(value: ((uint)Math.Sqrt(d: uint.CreateTruncating(value: value)))),
64 => T.CreateTruncating(value: Sqrt(value: ulong.CreateTruncating(value: value))),
#endif
_ => SoftwareImplementation(value: value),
};
/*
Credit goes to njuffa for providing a reference implementation:
https://stackoverflow.com/a/31149161/1186165
Notes:
- This implementation of the algorithm runs in constant time, based on the size of T.
- Ignoring the loop that is entered when the size of T exceeds 64, all branches get eliminated during JIT compilation.
*/
static T SoftwareImplementation(T value) {
var msb = int.CreateTruncating(value: value.MostSignificantBit());
var msbIsOdd = (msb & 1);
var m = ((msb + 1) >> 1);
var mMinusOne = (m - 1);
var mPlusOne = (m + 1);
var x = (T.One << mMinusOne);
var y = (x - (value >> (mPlusOne - msbIsOdd)));
var z = y;
x += x;
if (BinaryIntegerConstants<T>.Size > 8) {
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
}
if (BinaryIntegerConstants<T>.Size > 16) {
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
}
if (BinaryIntegerConstants<T>.Size > 32) {
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
}
if (BinaryIntegerConstants<T>.Size > 64) {
var i = T.CreateTruncating(value: (BinaryIntegerConstants<T>.Size >> 3));
do {
i -= (T.One << 3);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
y = (((y * y) >> mPlusOne) + z);
} while (i != T.Zero);
}
y = (x - y);
x = T.CreateTruncating(value: msbIsOdd);
y -= BinaryIntegerConstants<T>.Size switch {
8 => (x * ((y * T.CreateChecked(value: 5UL)) >> 4)),
16 => (x * ((y * T.CreateChecked(value: 75UL)) >> 8)),
32 => (x * ((y * T.CreateChecked(value: 19195UL)) >> 16)),
64 => (x * ((y * T.CreateChecked(value: 1257966796UL)) >> 32)),
128 => (x * ((y * T.CreateChecked(value: 5402926248376769403UL)) >> 64)),
_ => throw new NotSupportedException(), // TODO: Research a way to calculate the proper constant at runtime.
};
x = (T.One << (BinaryIntegerConstants<T>.Size - 1));
y -= (value - (y * y)).IsGreaterThan(other: x);
if (BinaryIntegerConstants<T>.Size > 8) {
y -= (value - (y * y)).IsGreaterThan(other: x);
y -= (value - (y * y)).IsGreaterThan(other: x);
}
if (BinaryIntegerConstants<T>.Size > 32) {
y -= (value - (y * y)).IsGreaterThan(other: x);
y -= (value - (y * y)).IsGreaterThan(other: x);
y -= (value - (y * y)).IsGreaterThan(other: x);
}
return (y & (T.AllBitsSet >> 1));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static uint Sqrt(ulong value) {
var x = ((uint)Math.Sqrt(d: unchecked((long)value)));
var y = (unchecked(((ulong)x) * x) > value).As<uint>(); // ((x * x) > value) ? 1 : 0
var z = ((uint)(value >> 63)); // (64 == value.MostSignificantBit()) ? 1 : 0
return unchecked(x - (y | z));
}
}
}
/
and%
operations, your sieve is not going to be very fast. I would understand wheel factorization to mean, picking the product of the first k primes (2, 6, 30, 210, ...) and then quickly looking up the result modulo that wheel number. Alternatively, generating a repeating sequence of intervals to skip over to check only those values that are not divisible by any small prime. \$\endgroup\$%
operation gives you all the prime factors up tok
, by the Chinese Remainder Theorem. You might check multiplicity by dividing by the product of the factors and repeating; that’s a number of repetitions equal to the highest multiplicity of any wheel prime. \$\endgroup\$