Last active
December 8, 2023 11:21
-
-
Save o8que/c53bc7ba793f59aeeb257c21f48c2e8c to your computer and use it in GitHub Desktop.
This file contains 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
// 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