I have a working implementation to calculate modulo 7 of a 32 bit unsigned int but I'm having trouble with the 64 bit implementation. The 32 bit implementations were from this blog post (with a few bug fixes). I was able to get 64 bit versions working for modulo 3, 5, 15, and 6 but not 7. The math is a little over my head.
For reference, here is a gist with the full code .
Here's the working 32 bit:
static public uint Mersenne7(uint a)
{
a = (a >> 24) + (a & 0xFFFFFF); // sum base 2**24 digits
a = (a >> 12) + (a & 0xFFF); // sum base 2**12 digits
a = (a >> 6) + (a & 0x3F); // sum base 2**6 digits
a = (a >> 3) + (a & 0x7); // sum base 2**2 digits
a = (a >> 2) + (a & 0x7); // sum base 2**2 digits
if (a > 5) a = a - 6;
return a;
}
I made what seemed like the obvious extension, which worked for modulo 3, 5, and 15, but for mod 7 the results are all over the place with no obvious pattern (except the results all are under 7):
static public ulong Mersenne7(ulong a)
{
a = (a >> 48) + (a & 0xFFFFFFFFFFFF); // sum base 2**48 digits
a = (a >> 24) + (a & 0xFFFFFF); // sum base 2**24 digits
a = (a >> 12) + (a & 0xFFF); // sum base 2**12 digits
a = (a >> 6) + (a & 0x3F); // sum base 2**6 digits
a = (a >> 3) + (a & 0x7); // sum base 2**2 digits
a = (a >> 2) + (a & 0x7); // sum base 2**2 digits
if (a > 5) a = a - 6;
return a;
}
The same technique for 64 bits obviously doesn't work for mod 7. I've been trying some variations but I don't get anything noticeably better and I'm not sure how to work through it systematically.
I have benchmarked and shown that calculating modulo using shift and add for Mersenne numbers is faster than the built in modulus operator in my environment, and this is running in a tight loop in a hot path (index to a static size circular buffer). These lower value divisors are also more common than larger buffer sizes.
The maths behind this is actually pretty simple.
(Note for the maths part I'm using a^b
to mean "a to the power b" not "a xor b". These math parts are not supposed to be C# code)
The key trick is that you split a
into two pieces so that
a = b * 2^3 + c
where b = a / 2^3 = a >> 3
and c = a mod 2^3 = a & 0x7
Then
a mod 7 = ((b mod 7) * (2^3 mod 7) + c ) mod 7
but 2^3 mod 7 = 1
so
a mod 7 = ( b mod 7 + c ) mod 7 = (b + c) mod 7
We apply this trick several times using
1 = 2^3 mod 7 = 2^6 mod 7 = 2^12 mod 7 = 2^24 mod 7 = 2^48 mod 7
With this in mind it looks like your "working" Mersene7 doesn't work.
I think this:
static public uint Mersenne7(uint a)
{
a = (a >> 24) + (a & 0xFFFFFF); // sum base 2**24 digits
a = (a >> 12) + (a & 0xFFF); // sum base 2**12 digits
a = (a >> 6) + (a & 0x3F); // sum base 2**6 digits
a = (a >> 3) + (a & 0x7); // sum base 2**2 digits
a = (a >> 2) + (a & 0x7); // sum base 2**2 digits
if (a > 5) a = a - 6;
return a;
}
should be
static public uint Mersenne7(uint a)
{
a = (a >> 24) + (a & 0xFFFFFF); // sum base 2**24 digits
a = (a >> 12) + (a & 0xFFF); // sum base 2**12 digits
a = (a >> 6) + (a & 0x3F); // sum base 2**6 digits
a = (a >> 3) + (a & 0x7); // sum base 2**3 digits
a = (a >> 3) + (a & 0x7); // sum base 2**3 digits
if (a >= 7) a = a - 7;
return a;
}
Note the change in the value in the final comparison, and the removal of the final sum line.
With these changes both the unit and ulong versions should produce correct results I think. (Haven't tested though)
I've duplicated the second contraction - I'm not sure if it is actually needed. (It is to handle overflow - but that may not occur you'll need to try some values to check)
In the ulong case you will need the a=(a>>48) + a & 0xFFFFFFFFFFFFL
line like you have already implemented.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.