flatten 20260225
This commit is contained in:
13
tjp/core/math/Float16.h
Normal file
13
tjp/core/math/Float16.h
Normal file
@@ -0,0 +1,13 @@
|
||||
// License: Modified MIT (NON-AI)
|
||||
// See the LICENSE file in the root directory for license information.
|
||||
// Copyright 2025 Timothy Prepscius
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace tjp::core {
|
||||
|
||||
struct Float16;
|
||||
|
||||
Float16& set(Float16 &lhs, float rhs);
|
||||
|
||||
} // namespace
|
||||
74
tjp/core/math/Float16.hpp
Normal file
74
tjp/core/math/Float16.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
// License: Modified MIT (NON-AI)
|
||||
// See the LICENSE file in the root directory for license information.
|
||||
// Copyright 2025 Timothy Prepscius
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../types/Types.h"
|
||||
#include "Float16.h"
|
||||
|
||||
namespace tjp::core {
|
||||
|
||||
struct Float16
|
||||
{
|
||||
using V = u16;
|
||||
V v;
|
||||
|
||||
struct Bits { V v; };
|
||||
|
||||
constexpr explicit Float16(Bits bits) :
|
||||
v(bits.v)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr Float16() :
|
||||
v(0)
|
||||
{}
|
||||
|
||||
constexpr Float16(const Float16 &rhs) :
|
||||
v(rhs.v)
|
||||
{}
|
||||
|
||||
constexpr Float16(float rhs) :
|
||||
v(convert(rhs))
|
||||
{
|
||||
}
|
||||
|
||||
constexpr Float16(double rhs) :
|
||||
v(convert((float)rhs))
|
||||
{
|
||||
}
|
||||
|
||||
static constexpr V convert(float rhs);
|
||||
static constexpr float convert(V rhs);
|
||||
|
||||
static constexpr Float16 max();
|
||||
static constexpr Float16 min__();
|
||||
static constexpr Float16 negative_max();
|
||||
|
||||
auto ordered() const
|
||||
{
|
||||
u16 sign = v >> 15;
|
||||
if (sign == 0)
|
||||
return v | 0x8000;
|
||||
else
|
||||
return ~v;
|
||||
}
|
||||
|
||||
operator float() const;
|
||||
operator double() const;
|
||||
|
||||
Float16 &operator =(float rhs)
|
||||
{
|
||||
return set(*this, rhs);
|
||||
}
|
||||
|
||||
Float16 &operator =(double rhs)
|
||||
{
|
||||
return set(*this, rhs);
|
||||
}
|
||||
} ;
|
||||
|
||||
} // namespace
|
||||
|
||||
#include "Float16.inl"
|
||||
186
tjp/core/math/Float16.inl
Normal file
186
tjp/core/math/Float16.inl
Normal file
@@ -0,0 +1,186 @@
|
||||
// License: Modified MIT (NON-AI)
|
||||
// See the LICENSE file in the root directory for license information.
|
||||
// Copyright 2025 Timothy Prepscius
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace tjp::core {
|
||||
|
||||
inline
|
||||
Float16::operator float() const
|
||||
{
|
||||
return convert(v);
|
||||
}
|
||||
|
||||
inline
|
||||
Float16::operator double() const
|
||||
{
|
||||
return (float)*this;
|
||||
}
|
||||
|
||||
inline
|
||||
bool operator <(const Float16 &lhs, const Float16 &rhs)
|
||||
{
|
||||
return lhs.ordered() < rhs.ordered();
|
||||
}
|
||||
|
||||
inline
|
||||
bool operator >(const Float16 &lhs, const Float16 &rhs)
|
||||
{
|
||||
return lhs.ordered() > rhs.ordered();
|
||||
}
|
||||
|
||||
inline
|
||||
bool operator ==(const Float16 &lhs, const Float16 &rhs)
|
||||
{
|
||||
return lhs.v == rhs.v;
|
||||
}
|
||||
|
||||
inline
|
||||
bool operator !=(const Float16 &lhs, const Float16 &rhs)
|
||||
{
|
||||
return lhs.v != rhs.v;
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
inline
|
||||
constexpr Float16 Float16::max()
|
||||
{
|
||||
return Float16(Float16::Bits { .v = 0x7BFF });
|
||||
}
|
||||
|
||||
inline
|
||||
constexpr Float16 Float16::negative_max()
|
||||
{
|
||||
return Float16(Float16::Bits { .v = 0xFBFF });
|
||||
}
|
||||
|
||||
inline
|
||||
constexpr Float16 Float16::min__()
|
||||
{
|
||||
return Float16(Float16::Bits { .v = 0x0400 });
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
union Float16_Float32Bits {
|
||||
float f;
|
||||
uint32_t u;
|
||||
};
|
||||
|
||||
inline
|
||||
constexpr float Float16::convert(V v)
|
||||
{
|
||||
uint16_t hv = v;
|
||||
uint16_t sign = (hv >> 15) & 0x1;
|
||||
uint16_t exp = (hv >> 10) & 0x1F;
|
||||
uint16_t frac = hv & 0x3FF;
|
||||
|
||||
uint32_t sign32 = sign << 31;
|
||||
uint32_t exp32 = 0, frac32 = 0;
|
||||
|
||||
if (exp == 0) {
|
||||
if (frac == 0) {
|
||||
// Zero
|
||||
exp32 = 0;
|
||||
frac32 = 0;
|
||||
} else {
|
||||
// Subnormal: normalize
|
||||
int e = -14; // unbiased exponent for half subnormals
|
||||
uint32_t f = frac;
|
||||
while ((f & 0x400) == 0) {
|
||||
f <<= 1;
|
||||
e--;
|
||||
}
|
||||
f &= 0x3FF;
|
||||
exp32 = (e + 127) << 23;
|
||||
frac32 = f << 13;
|
||||
}
|
||||
}
|
||||
else if (exp == 0x1F) {
|
||||
// Inf or NaN
|
||||
exp32 = 0xFF << 23;
|
||||
if (frac == 0) {
|
||||
// Inf
|
||||
frac32 = 0;
|
||||
} else {
|
||||
// NaN – preserve payload, set quiet NaN bit
|
||||
frac32 = (frac << 13) | 0x400000;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Normalized
|
||||
int e = exp - 15 + 127;
|
||||
exp32 = (uint32_t)e << 23;
|
||||
frac32 = (uint32_t)frac << 13;
|
||||
}
|
||||
|
||||
auto u = sign32 | exp32 | frac32;
|
||||
Float16_Float32Bits result { .u = u };
|
||||
return result.f;
|
||||
}
|
||||
|
||||
inline
|
||||
constexpr Float16::V Float16::convert(float rhs)
|
||||
{
|
||||
Float16_Float32Bits result { .f = rhs };
|
||||
uint32_t bits = result.u;
|
||||
|
||||
uint32_t sign = (bits >> 31) & 0x1;
|
||||
int32_t exp = (bits >> 23) & 0xFF;
|
||||
uint32_t frac = bits & 0x7FFFFF;
|
||||
|
||||
uint16_t sign16 = sign << 15;
|
||||
uint16_t exp16 = 0;
|
||||
uint16_t frac16 = 0;
|
||||
|
||||
if (exp == 0) {
|
||||
// Zero or subnormal in float
|
||||
exp16 = 0;
|
||||
frac16 = 0; // Flush subnormals to zero
|
||||
}
|
||||
else if (exp == 0xFF) {
|
||||
// Inf or NaN
|
||||
exp16 = 0x1F;
|
||||
frac16 = frac ? 0x200 : 0; // Preserve a quiet NaN bit
|
||||
}
|
||||
else {
|
||||
// Normalized float
|
||||
int newExp = exp - 127 + 15;
|
||||
if (newExp >= 0x1F) {
|
||||
// Overflow → Inf
|
||||
exp16 = 0x1F;
|
||||
frac16 = 0;
|
||||
}
|
||||
else if (newExp <= 0) {
|
||||
// Underflow → subnormal or zero
|
||||
if (newExp < -10) {
|
||||
exp16 = 0;
|
||||
frac16 = 0;
|
||||
}
|
||||
else {
|
||||
// Subnormal half
|
||||
uint32_t mant = (frac | 0x800000) >> (1 - newExp);
|
||||
frac16 = mant >> 13;
|
||||
exp16 = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
exp16 = newExp;
|
||||
frac16 = frac >> 13;
|
||||
}
|
||||
}
|
||||
|
||||
Float16::V v = sign16 | (exp16 << 10) | frac16;
|
||||
return v;
|
||||
}
|
||||
|
||||
inline
|
||||
Float16& set(Float16 &lhs, float rhs)
|
||||
{
|
||||
lhs.v = Float16::convert(rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
113
tjp/core/math/_tests/Float16.cpp
Executable file
113
tjp/core/math/_tests/Float16.cpp
Executable file
@@ -0,0 +1,113 @@
|
||||
// License: Modified MIT (NON-AI)
|
||||
// See the LICENSE file in the root directory for license information.
|
||||
// Copyright 2025 Timothy Prepscius
|
||||
|
||||
#include <tjp/core/math/Float16.hpp>
|
||||
|
||||
// License: Modified MIT (NON-AI)
|
||||
// See the LICENSE file in the root directory for license information.
|
||||
// Copyright 2025 Timothy Prepscius
|
||||
|
||||
#include <tjp/core/testing/catch.hpp>
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace tjp::core {
|
||||
namespace {
|
||||
|
||||
float randomFloat() {
|
||||
static std::random_device rd;
|
||||
static std::mt19937 gen(rd());
|
||||
static std::uniform_int_distribution<uint32_t> dist;
|
||||
|
||||
uint32_t bits = dist(gen);
|
||||
float value;
|
||||
std::memcpy(&value, &bits, sizeof(value));
|
||||
return value;
|
||||
}
|
||||
|
||||
float randomFloatInRange(float l, float r) {
|
||||
static std::random_device rd;
|
||||
static std::mt19937 gen(rd());
|
||||
static std::uniform_real_distribution<float> dist(l, r);
|
||||
|
||||
return dist(gen);
|
||||
}
|
||||
|
||||
SCENARIO("core::math::Float16")
|
||||
{
|
||||
GIVEN("nothing")
|
||||
{
|
||||
WHEN("tested all floats")
|
||||
{
|
||||
bool all_passed = true;
|
||||
|
||||
auto testCount = 100000;
|
||||
for (auto i=0; i<testCount; ++i)
|
||||
{
|
||||
auto a = randomFloat();
|
||||
|
||||
auto t = Float16(a);
|
||||
auto b = (float)t;
|
||||
|
||||
auto r16_min = (float)Float16::negative_max();
|
||||
auto r16_max = (float)Float16::max();
|
||||
|
||||
if (isnan(b))
|
||||
{
|
||||
auto was_nan = isnan(a);
|
||||
all_passed = all_passed && was_nan;
|
||||
}
|
||||
else
|
||||
if (isinf(b))
|
||||
{
|
||||
auto was_out_of_range = isinf(a) || a > r16_max || a < r16_min;
|
||||
all_passed = all_passed && was_out_of_range;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto relative_epsilon = 0.001f; // relative tolerance
|
||||
auto absolute_epsilon = 0.001f; // absolute tolerance
|
||||
auto d = std::abs(b - a);
|
||||
auto epsilon = std::max(relative_epsilon * std::abs(a), absolute_epsilon);
|
||||
all_passed = all_passed && (d < epsilon);
|
||||
|
||||
if (!all_passed)
|
||||
REQUIRE(false);
|
||||
}
|
||||
|
||||
if (!all_passed)
|
||||
REQUIRE(false);
|
||||
}
|
||||
|
||||
REQUIRE(all_passed);
|
||||
}
|
||||
|
||||
WHEN("tested valid float16s")
|
||||
{
|
||||
bool all_passed = true;
|
||||
auto testCount = 100000;
|
||||
for (auto i=0; i<testCount; ++i)
|
||||
{
|
||||
auto r16_min = (float)Float16::negative_max();
|
||||
auto r16_max = (float)Float16::max();
|
||||
|
||||
auto a = randomFloatInRange(r16_min, r16_max);
|
||||
|
||||
auto t = Float16(a);
|
||||
auto b = (float)t;
|
||||
|
||||
auto relative_epsilon = 0.001f; // relative tolerance
|
||||
auto absolute_epsilon = 0.001f; // absolute tolerance
|
||||
auto d = std::abs(b - a);
|
||||
auto epsilon = std::max(relative_epsilon * std::abs(a), absolute_epsilon);
|
||||
all_passed = all_passed && (d < epsilon);
|
||||
}
|
||||
REQUIRE(all_passed);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace
|
||||
Reference in New Issue
Block a user