Created
July 26, 2018 06:00
-
-
Save catid/df2e01f0d85552237f61ed9e6d512eaf to your computer and use it in GitHub Desktop.
This is a bad erasure code where it has to send some number of extra bits for each 32-bit word of input data. But it does work, which is neat.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
// Convolutional encoder: | |
s = a * x + b * y; | |
t = c * x + d * y; | |
The problem with this one is that the "ezc" number of low bit zeros in e is also a number of additional bits that must be sent with each recovery word. | |
Specifically in the decoder this line: | |
> uint64_t yr = ei * t >> ezc; | |
So to tolerate ezc = 4 zero bits, we need to make t 4 bits longer. | |
So each 32-bit word of input data becomes 4 bits longer to make the math work out. | |
Not so great. | |
But! It does work. | |
*/ | |
#include <stdint.h> | |
#include <iostream> | |
#include <iomanip> | |
using namespace std; | |
class PCGRandom | |
{ | |
public: | |
void Seed(uint64_t y, uint64_t x = 0) | |
{ | |
State = 0; | |
Inc = (y << 1u) | 1u; | |
Next(); | |
State += x; | |
Next(); | |
} | |
uint32_t Next() | |
{ | |
const uint64_t oldstate = State; | |
State = oldstate * UINT64_C(6364136223846793005) + Inc; | |
const uint32_t xorshifted = (uint32_t)(((oldstate >> 18) ^ oldstate) >> 27); | |
const uint32_t rot = oldstate >> 59; | |
return (xorshifted >> rot) | (xorshifted << ((uint32_t)(-(int32_t)rot) & 31)); | |
} | |
uint64_t Next64() | |
{ | |
return ((uint64_t)Next() << 32) | Next(); | |
} | |
uint64_t State = 0, Inc = 0; | |
}; | |
/* | |
65-bit multiplication of two 64-bit values a, b | |
with low bits set to 1 (implicitly): | |
(2a + 1)(2b + 1) | |
= 4ab + 2a + 2b + 1 | |
Stuffing it back into a 64-bit value: | |
= 2ab + a + b | |
*/ | |
// Product of a and b | |
static uint64_t mul65(uint64_t a, uint64_t b) | |
{ | |
return ((a * b) << 1) + (a + b); | |
} | |
// Multiplicative inverse of a | |
static uint64_t inv65(uint64_t a) | |
{ | |
uint64_t x = mul65(1, a) ^ 1; // 5 bit accurate guess | |
x = mul65(x, 1 + ~mul65(a, x)); // 10 bits | |
x = mul65(x, 1 + ~mul65(a, x)); // 20 bits | |
x = mul65(x, 1 + ~mul65(a, x)); // 40 bits | |
x = mul65(x, 1 + ~mul65(a, x)); // 80 bits | |
return x; | |
} | |
// Addition rule is: (2a+1) + (2b+1) => 2(a+b)+1+1-1 | |
// So it's normal addition (a+b) | |
// Subtraction rule is: (2a+1) - (2b+1) => 2(a-b)+1-1+1 | |
// So it's normal subtraction (a-b) | |
uint64_t f64(uint64_t x, uint64_t y) | |
{ | |
return y * (2 - y * x); | |
} | |
static uint64_t inv64(uint64_t x) | |
{ | |
uint64_t y = x; | |
y = f64(x, y); | |
y = f64(x, y); | |
y = f64(x, y); | |
y = f64(x, y); | |
y = f64(x, y); | |
return y; | |
} | |
uint16_t f16(uint16_t x, uint16_t y) | |
{ | |
return y * (2 - y * x); | |
} | |
static uint16_t inv16(uint16_t x) | |
{ | |
uint16_t y = x; | |
y = f16(x, y); | |
y = f16(x, y); | |
y = f16(x, y); | |
y = f16(x, y); | |
return y; | |
} | |
static bool test[65536]; | |
/* | |
Proved to myself that: | |
inv(odd) * n is a 1:1 map | |
*/ | |
static unsigned LowZeroBitCount(uint64_t x) | |
{ | |
unsigned count = 0; | |
while (x > 0) | |
{ | |
if (x & 1) { | |
break; | |
} | |
++count; | |
x >>= 1; | |
} | |
return count; | |
} | |
static void TestOddInvMul() | |
{ | |
/* | |
This experimentally verifies two things: | |
(1) That you can use a 64-bit (or generally larger) multiplicative inverse for a 16-bit finite field. | |
This means that I could use for example a 64-bit register for a 32-bit field potentially. | |
(2) That mulinv(x) * y is a bijective map for all y, when x is odd. | |
*/ | |
for (unsigned e = 1; e < 65536; e += 2) | |
{ | |
uint64_t mi = inv64(e); | |
memset(test, 0, sizeof(test)); | |
for (unsigned y = 0; y < 65536; ++y) | |
{ | |
uint64_t t = mi * y; | |
uint16_t p = (uint16_t)t; | |
if (test[p]) | |
{ | |
cout << " NOT BIJECTIVE for e = " << e << ", y = " << y << endl; | |
} | |
else | |
{ | |
test[p] = true; | |
} | |
} | |
} | |
} | |
static void TestEvenInvMul() | |
{ | |
// t = e^-1 * y | |
// y, e are even. | |
// I've found that multiplying e^-1 is a 1:1 bijective map for all 64-bit y (even too!) | |
// So let's treat it as a special map function instead of a field value we calculate. | |
// e^-1 * y = INVMUL(e, y). | |
for (unsigned e = 3; e < 65536; ++e) | |
{ | |
// e = 2^n * m | |
unsigned n = LowZeroBitCount(e); | |
uint64_t m = e >> n; | |
uint64_t mi = inv64(m); | |
memset(test, 0, sizeof(test)); | |
for (unsigned y = 0; y < 65536; ++y) | |
{ | |
// y = 2^u * v | |
unsigned u = LowZeroBitCount(y); | |
uint64_t v = y >> u; | |
uint64_t vi = inv64(v); | |
// t = 2^-n * m^-1 * 2^u * v | |
// t = m^-1 * 2^(u-n) * v | |
// t = INVMUL(m, 2^(u-n) * v) | |
// Focus on u >= n for now. | |
if (u < n) { | |
continue; | |
} | |
// We actually need INVMUL to work over 2^(16 + u - n) *not* 2^16! | |
// If we do this then we can recover the high bits properly! | |
uint64_t t = mi * (v << (u - n)); | |
t >>= (u - n); | |
uint16_t p = (uint16_t)t; | |
if (test[p]) | |
{ | |
cout << " NOT BIJECTIVE for e = " << e << ", y = " << y << endl; | |
} | |
else | |
{ | |
test[p] = true; | |
} | |
} | |
} | |
} | |
void main() | |
{ | |
//TestOddInvMul(); | |
PCGRandom prng; | |
prng.Seed(0); | |
uint64_t x, y; | |
uint64_t a, b, c, d; | |
uint64_t s, t; | |
for (;;) | |
{ | |
x = prng.Next64(); | |
y = prng.Next64(); | |
a = prng.Next64(); | |
b = prng.Next64(); | |
c = prng.Next64(); | |
d = prng.Next64(); | |
#if 0 | |
// Convolutional encoder: | |
s = mul65(a, x) + mul65(b, y); | |
t = mul65(c, x) + mul65(d, y); | |
// Simulate Gaussian elimination decoder: | |
uint64_t ai = inv65(a); | |
uint64_t z = mul65(c, ai); | |
t -= mul65(z, s); | |
// z = c * ai | |
// t = cx + dy - z * (ax + by) | |
// = d * y - z * b * y | |
// = y * (d - z * b) | |
uint64_t e = d - mul65(z, b); | |
uint64_t ei = inv65(e); | |
uint64_t yr = mul65(ei, t); | |
s -= mul65(b, yr); | |
uint64_t xr = mul65(ai, s); | |
#else | |
//x |= 1; | |
//y |= 1; | |
a |= 1; | |
b |= 1; | |
c |= 1; | |
d |= 1; | |
//d--; - Trivially works | |
x = (uint32_t)x; | |
y = (uint32_t)y; | |
a = (uint32_t)a; | |
b = (uint32_t)b; | |
c = (uint32_t)c; | |
d = (uint32_t)d; | |
// Convolutional encoder: | |
s = a * x + b * y; | |
t = c * x + d * y; | |
//s = (uint32_t)s; | |
//t = (uint32_t)t; | |
// This implies we need to save a number of high bits = the number of zero low bits later. | |
//s = (s << 31) >> 31; | |
//t = (t << 31) >> 31; | |
// Simulate Gaussian elimination decoder: | |
uint64_t ai = inv64(a); | |
uint64_t z = c * ai; | |
//z = (uint32_t)z; | |
// mulsub() large data operation ---- | |
t -= z * s; | |
//t = (uint32_t)t; | |
// z = c * ai | |
// t = cx + dy - z * (ax + by) | |
// = d * y - z * b * y | |
// = y * (d - z * b) | |
uint64_t e = d - z * b; | |
unsigned ezc = 0; | |
//cout << "e="; | |
for (int i = 0; i < 64; ++i) | |
{ | |
if ((e &(1ull << i)) == 0) | |
{ | |
++ezc; | |
//cout << "0"; | |
} | |
else { | |
break; | |
} | |
} | |
//cout << endl; | |
#if 1 | |
unsigned tzc = 0; | |
//cout << "t="; | |
for (int i = 0; i < 64; ++i) | |
{ | |
if ((t &(1ull << i)) == 0) | |
{ | |
++tzc; | |
//cout << "0"; | |
} | |
else { | |
break; | |
} | |
} | |
//cout << endl; | |
if (tzc < ezc) { | |
cout << "FAIL" << endl; | |
return; | |
} | |
#endif | |
uint64_t ei = inv64(e >> ezc); | |
// mul() large data operation ---- | |
//uint64_t yr = (ei * (t >> tzc)) << (tzc - ezc); | |
uint64_t yr = ei * t >> ezc; | |
yr = (uint32_t)yr; | |
// t = v << u | |
// ((v << u) >> u) << (u - n), u >= n | |
// = - n | |
#if 0 | |
unsigned yrzc = 0; | |
cout << "yr="; | |
for (int i = 0; i < 64; ++i) | |
{ | |
if ((yr &(1ull << i)) == 0) | |
{ | |
++yrzc; | |
cout << "0"; | |
} | |
else { | |
break; | |
} | |
} | |
cout << endl; | |
#endif | |
// mulsub() large data operation ---- | |
s -= b * yr; | |
s = (uint32_t)s; | |
// mul() large data operation ---- | |
uint64_t xr = ai * s; | |
xr = (uint32_t)xr; | |
#endif | |
static unsigned win = 0; | |
static unsigned loss = 0; | |
if (y != yr) | |
{ | |
if (ezc <= 0) | |
{ | |
cout << "WAT" << endl; | |
return; | |
} | |
uint64_t z = (uint32_t)(yr ^ y) << ezc; | |
if (z) | |
{ | |
cout << "WAAAT" << endl; | |
return; | |
} | |
#if 0 | |
cout << "y = " << hex << setfill('0') << setw(8) << y << endl; | |
cout << "err = " << hex << setfill('0') << setw(8) << (yr ^ y) << endl; | |
cout << "ezc = " << dec << ezc << endl; | |
cout << "tzc = " << dec << tzc << endl; | |
cout << "tzc - ezc = " << dec << tzc - ezc << endl; | |
cout << "!!!! FAIL Y" << endl; | |
#endif | |
++loss; | |
} | |
else if (x != xr) | |
{ | |
cout << "!!!! FAIL X" << endl; | |
++loss; | |
} | |
else | |
{ | |
#if 0 | |
cout << "y = " << hex << setfill('0') << setw(8) << y << endl; | |
cout << "ezc = " << dec << ezc << endl; | |
cout << "tzc = " << dec << tzc << endl; | |
cout << "tzc - ezc = " << dec << tzc - ezc << endl; | |
#endif | |
cout << "SUCCESS RATE = " << win / (float)(win + loss) << endl; | |
++win; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment