Compact serialization for amounts
Special serializer/deserializer for amount values. It is optimized for values which have few non-zero digits in decimal representation. Most amounts currently in the txout set take only 1 or 2 bytes to represent.
This commit is contained in:
parent
69fc8047a9
commit
0fa593d0fb
3 changed files with 130 additions and 3 deletions
54
src/main.cpp
54
src/main.cpp
|
@ -4058,3 +4058,57 @@ void GenerateBitcoins(bool fGenerate, CWallet* pwallet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Amount compression:
|
||||||
|
// * If the amount is 0, output 0
|
||||||
|
// * first, divide the amount (in base units) by the largest power of 10 possible; call the exponent e (e is max 9)
|
||||||
|
// * if e<9, the last digit of the resulting number cannot be 0; store it as d, and drop it (divide by 10)
|
||||||
|
// * call the result n
|
||||||
|
// * output 1 + 10*(9*n + d - 1) + e
|
||||||
|
// * if e==9, we only know the resulting number is not zero, so output 1 + 10*(n - 1) + 9
|
||||||
|
// (this is decodable, as d is in [1-9] and e is in [0-9])
|
||||||
|
|
||||||
|
uint64 CTxOutCompressor::CompressAmount(uint64 n)
|
||||||
|
{
|
||||||
|
if (n == 0)
|
||||||
|
return 0;
|
||||||
|
int e = 0;
|
||||||
|
while (((n % 10) == 0) && e < 9) {
|
||||||
|
n /= 10;
|
||||||
|
e++;
|
||||||
|
}
|
||||||
|
if (e < 9) {
|
||||||
|
int d = (n % 10);
|
||||||
|
assert(d >= 1 && d <= 9);
|
||||||
|
n /= 10;
|
||||||
|
return 1 + (n*9 + d - 1)*10 + e;
|
||||||
|
} else {
|
||||||
|
return 1 + (n - 1)*10 + 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64 CTxOutCompressor::DecompressAmount(uint64 x)
|
||||||
|
{
|
||||||
|
// x = 0 OR x = 1+10*(9*n + d - 1) + e OR x = 1+10*(n - 1) + 9
|
||||||
|
if (x == 0)
|
||||||
|
return 0;
|
||||||
|
x--;
|
||||||
|
// x = 10*(9*n + d - 1) + e
|
||||||
|
int e = x % 10;
|
||||||
|
x /= 10;
|
||||||
|
uint64 n = 0;
|
||||||
|
if (e < 9) {
|
||||||
|
// x = 9*n + d - 1
|
||||||
|
int d = (x % 9) + 1;
|
||||||
|
x /= 9;
|
||||||
|
// x = n
|
||||||
|
n = x*10 + d;
|
||||||
|
} else {
|
||||||
|
n = x+1;
|
||||||
|
}
|
||||||
|
while (e) {
|
||||||
|
n *= 10;
|
||||||
|
e--;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
17
src/main.h
17
src/main.h
|
@ -652,14 +652,25 @@ class CTxOutCompressor
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
CTxOut &txout;
|
CTxOut &txout;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
static uint64 CompressAmount(uint64 nAmount);
|
||||||
|
static uint64 DecompressAmount(uint64 nAmount);
|
||||||
|
|
||||||
CTxOutCompressor(CTxOut &txoutIn) : txout(txoutIn) { }
|
CTxOutCompressor(CTxOut &txoutIn) : txout(txoutIn) { }
|
||||||
|
|
||||||
IMPLEMENT_SERIALIZE(
|
IMPLEMENT_SERIALIZE(({
|
||||||
READWRITE(VARINT(txout.nValue));
|
if (!fRead) {
|
||||||
|
uint64 nVal = CompressAmount(txout.nValue);
|
||||||
|
READWRITE(VARINT(nVal));
|
||||||
|
} else {
|
||||||
|
uint64 nVal = 0;
|
||||||
|
READWRITE(VARINT(nVal));
|
||||||
|
txout.nValue = DecompressAmount(nVal);
|
||||||
|
}
|
||||||
CScriptCompressor cscript(REF(txout.scriptPubKey));
|
CScriptCompressor cscript(REF(txout.scriptPubKey));
|
||||||
READWRITE(cscript);
|
READWRITE(cscript);
|
||||||
)
|
});)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
62
src/test/compress_tests.cpp
Normal file
62
src/test/compress_tests.cpp
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
#include <boost/test/unit_test.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "main.h"
|
||||||
|
|
||||||
|
// amounts 0.00000001 .. 0.00100000
|
||||||
|
#define NUM_MULTIPLES_UNIT 100000
|
||||||
|
|
||||||
|
// amounts 0.01 .. 100.00
|
||||||
|
#define NUM_MULTIPLES_CENT 10000
|
||||||
|
|
||||||
|
// amounts 1 .. 10000
|
||||||
|
#define NUM_MULTIPLES_1BTC 10000
|
||||||
|
|
||||||
|
// amounts 50 .. 21000000
|
||||||
|
#define NUM_MULTIPLES_50BTC 420000
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE(compress_tests)
|
||||||
|
|
||||||
|
bool static TestEncode(uint64 in) {
|
||||||
|
return in == CTxOutCompressor::DecompressAmount(CTxOutCompressor::CompressAmount(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool static TestDecode(uint64 in) {
|
||||||
|
return in == CTxOutCompressor::CompressAmount(CTxOutCompressor::DecompressAmount(in));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool static TestPair(uint64 dec, uint64 enc) {
|
||||||
|
return CTxOutCompressor::CompressAmount(dec) == enc &&
|
||||||
|
CTxOutCompressor::DecompressAmount(enc) == dec;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(compress_amounts)
|
||||||
|
{
|
||||||
|
BOOST_CHECK(TestPair( 0, 0x0));
|
||||||
|
BOOST_CHECK(TestPair( 1, 0x1));
|
||||||
|
BOOST_CHECK(TestPair( CENT, 0x7));
|
||||||
|
BOOST_CHECK(TestPair( COIN, 0x9));
|
||||||
|
BOOST_CHECK(TestPair( 50*COIN, 0x32));
|
||||||
|
BOOST_CHECK(TestPair(21000000*COIN, 0x1406f40));
|
||||||
|
|
||||||
|
for (uint64 i = 1; i <= NUM_MULTIPLES_UNIT; i++)
|
||||||
|
BOOST_CHECK(TestEncode(i));
|
||||||
|
|
||||||
|
for (uint64 i = 1; i <= NUM_MULTIPLES_CENT; i++)
|
||||||
|
BOOST_CHECK(TestEncode(i * CENT));
|
||||||
|
|
||||||
|
for (uint64 i = 1; i <= NUM_MULTIPLES_1BTC; i++)
|
||||||
|
BOOST_CHECK(TestEncode(i * COIN));
|
||||||
|
|
||||||
|
for (uint64 i = 1; i <= NUM_MULTIPLES_50BTC; i++)
|
||||||
|
BOOST_CHECK(TestEncode(i * 50 * COIN));
|
||||||
|
|
||||||
|
for (uint64 i = 0; i < 100000; i++)
|
||||||
|
BOOST_CHECK(TestDecode(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END()
|
Loading…
Reference in a new issue