feature: Utils.BitsRequired to prepare for BitTree delta compression

This commit is contained in:
vis2k 2022-10-18 16:25:19 +02:00
parent 706bbaa3b0
commit 339a9d9967
2 changed files with 153 additions and 0 deletions

View File

@ -80,6 +80,106 @@ public static int RoundBitsToFullBytes(int bits)
return ((bits - 1) / 8) + 1;
}
// calculate bits needed for a value range
// largest type we support is ulong, so use that as parameters
// min, max are both INCLUSIVE
// min=0, max=7 means 0..7 = 8 values in total = 3 bits required
public static int BitsRequired(ulong min, ulong max)
{
// make sure value is within range
// => throws exception because the developer should fix it immediately
if (min > max)
throw new ArgumentOutOfRangeException($"{nameof(BitsRequired)} min={min} needs to be <= max={max}");
// if min == max then we need 0 bits because it is only ever one value
if (min == max)
return 0;
// normalize from min..max to 0..max-min
// example:
// min = 0, max = 7 => 7-0 = 7 (0..7 = 8 values needed)
// min = 4, max = 7 => 7-4 = 3 (0..3 = 4 values needed)
//
// CAREFUL: DO NOT ADD ANYTHING TO THIS VALUE.
// if min=0 and max=ulong.max then normalized = ulong.max,
// adding anything to it would make it overflow!
// (see tests!)
ulong normalized = max - min;
//UnityEngine.Debug.Log($"min={min} max={max} normalized={normalized}");
// .Net Core 3.1 has BitOperations.Log2(x)
// Unity doesn't, so we could use one of a dozen weird tricks:
// https://stackoverflow.com/questions/15967240/fastest-implementation-of-log2int-and-log2float
// including lookup tables, float exponent tricks for little endian,
// etc.
//
// ... or we could just hard code!
if (normalized < 2) return 1;
if (normalized < 4) return 2;
if (normalized < 8) return 3;
if (normalized < 16) return 4;
if (normalized < 32) return 5;
if (normalized < 64) return 6;
if (normalized < 128) return 7;
if (normalized < 256) return 8;
if (normalized < 512) return 9;
if (normalized < 1024) return 10;
if (normalized < 2048) return 11;
if (normalized < 4096) return 12;
if (normalized < 8192) return 13;
if (normalized < 16384) return 14;
if (normalized < 32768) return 15;
if (normalized < 65536) return 16;
if (normalized < 131072) return 17;
if (normalized < 262144) return 18;
if (normalized < 524288) return 19;
if (normalized < 1048576) return 20;
if (normalized < 2097152) return 21;
if (normalized < 4194304) return 22;
if (normalized < 8388608) return 23;
if (normalized < 16777216) return 24;
if (normalized < 33554432) return 25;
if (normalized < 67108864) return 26;
if (normalized < 134217728) return 27;
if (normalized < 268435456) return 28;
if (normalized < 536870912) return 29;
if (normalized < 1073741824) return 30;
if (normalized < 2147483648) return 31;
if (normalized < 4294967296) return 32;
if (normalized < 8589934592) return 33;
if (normalized < 17179869184) return 34;
if (normalized < 34359738368) return 35;
if (normalized < 68719476736) return 36;
if (normalized < 137438953472) return 37;
if (normalized < 274877906944) return 38;
if (normalized < 549755813888) return 39;
if (normalized < 1099511627776) return 40;
if (normalized < 2199023255552) return 41;
if (normalized < 4398046511104) return 42;
if (normalized < 8796093022208) return 43;
if (normalized < 17592186044416) return 44;
if (normalized < 35184372088832) return 45;
if (normalized < 70368744177664) return 46;
if (normalized < 140737488355328) return 47;
if (normalized < 281474976710656) return 48;
if (normalized < 562949953421312) return 49;
if (normalized < 1125899906842624) return 50;
if (normalized < 2251799813685248) return 51;
if (normalized < 4503599627370496) return 52;
if (normalized < 9007199254740992) return 53;
if (normalized < 18014398509481984) return 54;
if (normalized < 36028797018963968) return 55;
if (normalized < 72057594037927936) return 56;
if (normalized < 144115188075855872) return 57;
if (normalized < 288230376151711744) return 58;
if (normalized < 576460752303423488) return 59;
if (normalized < 1152921504606846976) return 60;
if (normalized < 2305843009213693952) return 61;
if (normalized < 4611686018427387904) return 62;
if (normalized < 9223372036854775808) return 63;
return 64;
}
public static bool IsPrefab(GameObject obj)
{
#if UNITY_EDITOR

View File

@ -1,3 +1,4 @@
using System;
using NUnit.Framework;
using UnityEngine;
@ -64,6 +65,58 @@ public void RoundBitsToFullBytes()
Assert.That(Utils.RoundBitsToFullBytes(i), Is.EqualTo(8));
}
[Test]
public void BitsRequired()
{
// min > max should never work
Assert.Throws<ArgumentOutOfRangeException>(() => Utils.BitsRequired(2, 1));
// 2..2 is only ever 1 value = 2, so 0 bits needed
Assert.That(Utils.BitsRequired(2, 2), Is.EqualTo(0));
// 3..4 are 2 values, so 1 bit
Assert.That(Utils.BitsRequired(3, 4), Is.EqualTo(1));
// 0..7 are 8 values, so 3 bits
Assert.That(Utils.BitsRequired(0, 7), Is.EqualTo(3));
// 4..7 are 4 values, so 2 bits
Assert.That(Utils.BitsRequired(4, 7), Is.EqualTo(2));
// 0..255 are 256 values, so 8 bits
Assert.That(Utils.BitsRequired(0, 255), Is.EqualTo(8));
// 128..255 are 128 values, so 7 bits
Assert.That(Utils.BitsRequired(128, 255), Is.EqualTo(7));
// ushort should always fit into 2 bytes
Assert.That(Utils.BitsRequired(0, ushort.MaxValue), Is.EqualTo(16));
// uint should always fit into 4 bytes
Assert.That(Utils.BitsRequired(0, uint.MaxValue), Is.EqualTo(32));
// ulong should always fit into 8 bytes
// this is the maximum range that ever fits into an ulong.
// a lot of things could go wrong, e.g. if we +1 to that range
// without being careful.
// THIS TEST IS HUGELY IMPORTANT.
Assert.That(Utils.BitsRequired(0, ulong.MaxValue), Is.EqualTo(64));
// since we hardcoded the values, let's test for 1..63 shifted bits
// just to be sure that all of the hard coded values are correct!
for (int bits = 1; bits < 64; ++bits)
{
// 1 bits => bitsMax = 1
// 2 bits => bitsMax = 2
// 3 bits => bitsMax = 4
// etc.
// so check 0..bitsMax-1
ulong bitsMax = 1ul << bits;
//UnityEngine.Debug.Log($"testing bitsMax={bitsMax-1} => bits={bits}");
Assert.That(Utils.BitsRequired(0, bitsMax - 1), Is.EqualTo(bits));
}
}
[Test]
public void IsPointInScreen()
{