Skip to content

Instantly share code, notes, and snippets.

@o8que
Last active December 8, 2023 11:21
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save o8que/c53bc7ba793f59aeeb257c21f48c2e8c to your computer and use it in GitHub Desktop.
Save o8que/c53bc7ba793f59aeeb257c21f48c2e8c to your computer and use it in GitHub Desktop.
// These codes are licensed under CC0.
// http://creativecommons.org/publicdomain/zero/1.0/deed.ja
using System;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;
namespace Ore2Lib
{
public sealed class LowPrecisionFloatFormatter
{
private readonly int signBits;
private readonly int exponentBits;
private readonly int fractionBits;
private readonly bool hasSignBits;
private readonly uint exponentMask;
private readonly uint fractionMask;
private readonly int signOffset;
private readonly int exponentOffset;
private readonly int fractionOffset;
private readonly bool reservedFor0;
private readonly bool reservedForNaN;
private readonly uint bias;
private readonly uint exponentMin;
private readonly uint exponentMax;
public LowPrecisionFloatFormatter(int signBits, int exponentBits, int fractionBits, uint excess = 255u, bool reservedFor0 = true, bool reservedForNaN = true) {
this.signBits = Mathf.Clamp(signBits, 0, 1);
this.exponentBits = Mathf.Clamp(exponentBits, 2, 8);
this.fractionBits = Mathf.Clamp(fractionBits, 1, 23);
hasSignBits = (this.signBits == 1);
exponentMask = (1u << this.exponentBits) - 1u;
fractionMask = (1u << this.fractionBits) - 1u;
signOffset = this.exponentBits + this.fractionBits;
exponentOffset = this.fractionBits;
fractionOffset = 23 - this.fractionBits;
this.reservedFor0 = reservedFor0;
this.reservedForNaN = reservedForNaN;
if (this.exponentBits == 8) {
this.reservedFor0 = true;
this.reservedForNaN = true;
bias = 127u;
} else if (excess == 255u) {
bias = (1u << (this.exponentBits - 1)) - 1u;
} else {
uint minBias = ((this.reservedFor0) ? 1u : 0u);
uint maxBias = exponentMask - ((this.reservedForNaN) ? 1u : 0u);
bias = Math.Min(Math.Max(minBias, excess), maxBias);
}
exponentMin = 127u - bias;
exponentMax = exponentMin + exponentMask;
}
public uint Serialize(float value) {
uint bytes = new Union(value).bytes;
uint sign = (bytes >> 31) & 0x1u;
uint exponent = (bytes >> 23) & 0xFFu;
uint fraction = bytes & 0x7FFFFFu;
if (!hasSignBits && sign == 1u) {
return 0u;
}
if (exponent < exponentMin || (reservedFor0 && exponent == exponentMin)) {
exponent = 0u;
fraction = 0u;
} else if (exponent < exponentMax || (!reservedForNaN && exponent == exponentMax)) {
exponent -= exponentMin;
fraction >>= fractionOffset;
} else if (!reservedForNaN) {
exponent = exponentMask;
fraction = fractionMask;
} else if (exponent < 0xFFu) {
exponent = exponentMask - 1u;
fraction = fractionMask;
} else {
exponent = exponentMask;
fraction >>= fractionOffset;
}
return (sign << signOffset) | (exponent << exponentOffset) | fraction;
}
public float Deserialize(uint bytes) {
uint sign = (hasSignBits) ? (bytes >> signOffset) & 0x1u : 0u;
uint exponent = (bytes >> exponentOffset) & exponentMask;
uint fraction = bytes & fractionMask;
if (reservedForNaN && exponent == exponentMask) {
exponent = 0xFFu;
} else if (!(reservedFor0 && exponent == 0u)) {
exponent += exponentMin;
}
return new Union((sign << 31) | (exponent << 23) | fraction << fractionOffset).value;
}
public override string ToString() {
return $"({signBits}, {exponentBits}, {fractionBits}, {bias}, {reservedFor0}, {reservedForNaN})";
}
public string ToDetailString() {
uint minE = ((reservedFor0) ? 1u : 0u) << exponentOffset;
uint maxE = (exponentMask - ((reservedForNaN) ? 1u : 0u)) << exponentOffset;
float minValue = Deserialize(minE);
float maxValue = Deserialize(maxE | fractionMask);
float minULP = Deserialize(minE | 1u) - Deserialize(minE);
float maxULP = Deserialize(maxE | 1u) - Deserialize(maxE);
var builder = new StringBuilder();
builder.AppendLine(ToString());
builder.AppendLine($"Range: ({minValue}, {maxValue})");
builder.AppendLine($"ULP: ({minULP}, {maxULP})");
return builder.ToString();
}
public string ToBinaryString(float value) {
uint bytes = new Union(value).bytes;
string sign = Convert.ToString((bytes >> 31) & 0x1u, 2);
string exponent = Convert.ToString((bytes >> 23) & 0xFFu, 2).PadLeft(8, '0');
string fraction = Convert.ToString(bytes & 0x7FFFFFu, 2).PadLeft(23, '0');
return $"{sign} {exponent} {fraction}";
}
public string ToBinaryString(uint bytes) {
string sign = Convert.ToString((bytes >> signOffset) & 0x1u, 2);
string exponent = Convert.ToString((bytes >> exponentOffset) & exponentMask, 2).PadLeft(exponentBits, '0');
string fraction = Convert.ToString(bytes & fractionMask, 2).PadLeft(fractionBits, '0');
return $"{sign} {exponent} {fraction}";
}
[StructLayout(LayoutKind.Explicit)]
private readonly ref struct Union
{
[FieldOffset(0)]
public readonly float value;
[FieldOffset(0)]
public readonly uint bytes;
public Union(float value) {
this.bytes = 0u;
this.value = value;
}
public Union(uint bytes) {
this.value = 0f;
this.bytes = bytes;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment