// CelerisLab – core/precision.cuh
// Storage precision abstraction (FP32 / FP16S / FP16C).
// Compute precision is always float (FP32).
//
// Controlled by macros.h:
//   STORE_PRECISION  0 = FP32 (default)
//                    1 = FP16S (IEEE-754 half + ×32768 scaling)
//                    2 = FP16C (custom 1-4-11 format, higher precision)
// ============================================================================

#ifndef CELERIS_CORE_PRECISION_CUH
#define CELERIS_CORE_PRECISION_CUH

#ifndef STORE_PRECISION
#define STORE_PRECISION 0
#endif

// ============================================================================
// FP16S – Scaled IEEE-754 Half  (hardware-accelerated conversion)
// ============================================================================
#if STORE_PRECISION == 1

#include <cuda_fp16.h>
using fpxx = __half;

__device__ __forceinline__ float load_ddf(const fpxx* p, unsigned long idx) {
    return __half2float(p[idx]) * 3.0517578125e-5f;   // ÷ 32768
}
__device__ __forceinline__ void store_ddf(fpxx* p, unsigned long idx, float v) {
    p[idx] = __float2half_rn(v * 32768.0f);
}

#define FPXX_BYTES 2

// ============================================================================
// FP16C – Custom 1-4-11 format  (software conversion, ~3.6 dec. digits)
// ============================================================================
#elif STORE_PRECISION == 2

using fpxx = unsigned short;

__device__ __forceinline__ float half_custom_to_float(unsigned short x) {
    unsigned int e = (x & 0x7800) >> 11;
    unsigned int m = (x & 0x07FF) << 12;
    unsigned int v = __float_as_uint((float)m) >> 23;
    return __uint_as_float(
        (x & 0x8000) << 16 |
        (e != 0) * ((e + 112) << 23 | m) |
        ((e == 0) & (m != 0)) * ((v - 37) << 23 | ((m << (150 - v)) & 0x007FF000))
    );
}

__device__ __forceinline__ unsigned short float_to_half_custom(float x) {
    unsigned int b = __float_as_uint(x) + 0x00000800;  // round-to-nearest-even
    unsigned int e = (b & 0x7F800000) >> 23;
    unsigned int m = b & 0x007FFFFF;
    return (unsigned short)(
        (b & 0x80000000) >> 16 |
        (e > 112) * ((((e - 112) << 11) & 0x7800) | m >> 12) |
        ((e < 113) & (e > 100)) * ((((0x007FF800 + m) >> (124 - e)) + 1) >> 1)
    );
}

__device__ __forceinline__ float load_ddf(const fpxx* p, unsigned long idx) {
    return half_custom_to_float(p[idx]);
}
__device__ __forceinline__ void store_ddf(fpxx* p, unsigned long idx, float v) {
    p[idx] = float_to_half_custom(v);
}

#define FPXX_BYTES 2

// ============================================================================
// FP32 – No conversion (default)
// ============================================================================
#else

using fpxx = float;

__device__ __forceinline__ float load_ddf(const fpxx* p, unsigned long idx) {
    return p[idx];
}
__device__ __forceinline__ void store_ddf(fpxx* p, unsigned long idx, float v) {
    p[idx] = v;
}

#define FPXX_BYTES 4

#endif  // STORE_PRECISION

#endif  // CELERIS_CORE_PRECISION_CUH
