Skip to content

Instantly share code, notes, and snippets.

@catid
Created July 26, 2018 06:00
Show Gist options
  • Save catid/df2e01f0d85552237f61ed9e6d512eaf to your computer and use it in GitHub Desktop.
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.
/*
// 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