packages feed

fp-ieee-0.1.0.7: cbits/half.c

#include <stdint.h> // uint16_t
#include <string.h>
#include <math.h>

/*
 * binary16
 *   p = 11
 *   emin = -14
 *   emax = 15
 *   floatRange _ = (-13,16)
 *   minPositive = 0x1p-24
 *   minPositiveNormal = 0x1p-14
 *   maxFinite = 0x1.ffcp15
 *   exponentBias = 15
 */

/*
 * binary32
 *   p = 24
 *   emin = -126
 *   emax = 127
 *   floatRange _ = (-125,128)
 *   minPositive = 0x1p-149
 *   minPositiveNormal = 0x1p-126
 *   maxFinite = 0x1.fffffep127
 *   exponentBias = 127
 */

/*
 * binary64
 *   p = 53
 *   emin = -1022
 *   emax = 1023
 *   floatRange _ = (-1021,1024)
 *   minPositive = 0x1p-1074
 *   minPositiveNormal = 0x1p-1022
 *   maxFinite = 0x1.ffff_ffff_ffff_fp1023
 *   exponentBias = 1023
 */

// TODO: Use AVX-512 FP16 if available

#if defined(__F16C__) // x86 F16C

#include <x86intrin.h>

uint16_t hs_fp_ieee_floatToHalf(float f)
{
    __m128 x = _mm_set_ss(f);
    union {
        __m128i v;
        uint16_t c;
    } u;
    // A floating-point exception can be raised
    u.v = _mm_cvtps_ph(x, _MM_FROUND_TO_NEAREST_INT); // VCVTPS2PH
    return u.c;
}

float hs_fp_ieee_halfToFloat(uint16_t c)
{
    union {
        __m128i v;
        uint16_t c;
    } u;
    u.c = c;
    __m128 w = _mm_cvtph_ps(u.v); // VCVTPH2PS
    float d;
    _mm_store_ss(&d, w);
    return d;
}

uint16_t hs_fp_ieee_doubleToHalf(double d)
{
    uint64_t q;
    memcpy(&q, &d, sizeof(d));
    const uint64_t F64_SIGN_MASK   = UINT64_C(0x8000000000000000);
    const uint64_t F64_EXP_MASK    = UINT64_C(0x7ff0000000000000);
    const uint64_t F64_MANT_MASK   = UINT64_C(0x000fffffffffffff);
    const uint64_t UPPER_MANT_MASK = UINT64_C(0x000ffc0000000000);
    const uint64_t LOWER_MANT_MASK = UINT64_C(0x000003ffffffffff);
    const uint64_t TIE             = UINT64_C(0x0000020000000000);
    uint16_t f16sign = (uint16_t)((q & F64_SIGN_MASK) >> (63 - 15));
    uint64_t biasedExp = (q & F64_EXP_MASK) >> 52;
    //        biasedExp - 1023 <= -26: round to zero
    //        biasedExp - 1023 == -25: if abs d == 0x1p-25 then +-0 else +-0x1p-24
    // -24 <= biasedExp - 1023 <= -16: subnormal
    //        biasedExp - 1023 == -15: if abs d >= 0x1.ffep-15 then +-0x1p-14 else subnormal
    // -14 <= biasedExp - 1023 <=  14: normal
    //        biasedExp - 1023 ==  15: if abs d >= 0x1.ffep15 then +-infinity else normal
    //  16 <= biasedExp - 1023       : infinity
    if (biasedExp < 1023 - 25) {
        // biasedExp - 1023 < -25: zero
        return f16sign | 0;
    } else if (biasedExp < 1023 - 14) {
        // biasedExp - 1023 < -14: zero / subnormal / minPositiveNormal
        // 2^(biasedExp - 1023) <= abs d < 2^(biasedExp - 1022)
        // ulp = 0x1p-24
        // 0b1XX..X * 0x1p-24
        //   ^^   ^
        //   ||   +- 2^(-24)
        //   | \         : (biasedExp - 1024) - (-24) + 1 = biasedExp - (1024 - 24 - 1)
        //    \ ---- 2^(biasedExp - 1024)
        //     ----- 2^(biasedExp - 1023)
        int bitWidth = biasedExp - (1024 - 24 - 1);
        uint64_t mant = (F64_MANT_MASK + 1) | (q & F64_MANT_MASK);
        uint16_t f16mant = (uint16_t)(mant >> (52 - bitWidth));
        uint64_t lower = mant & (((LOWER_MANT_MASK + 1) << (10 - bitWidth)) - 1);
        uint64_t tie = TIE << (10 - bitWidth);
        if (lower < tie || (lower == tie && (f16mant & 1) == 0)) {
            return f16sign | f16mant;
        } else {
            return (f16sign | f16mant) + 1; // may be +-minPositiveNormal
        }
    } else if (biasedExp < 1023 + 16) {
        // biasedExp - 1023 < 16: normal / infinity
        uint16_t f16mant = (uint16_t)((q & UPPER_MANT_MASK) >> (53 - 11));
        uint16_t f16exp = (uint16_t)((biasedExp - (1023 - 15)) << 10);
        uint64_t lower = q & LOWER_MANT_MASK;
        if (lower < TIE || (lower == TIE && (f16mant & 1) == 0)) {
            return f16sign | f16exp | f16mant;
        } else {
            return (f16sign | f16exp | f16mant) + 1; // may be +-infinity
        }
    } else {
        // infinity or NaN
        if (isnan(d)) {
            // keep the sign bit
            // discard the payload
            return f16sign | 0x7e00;
        } else {
            return f16sign | 0x7c00;
        }
    }
}

double hs_fp_ieee_halfToDouble(uint16_t c)
{
    union {
        __m128i v;
        uint16_t c;
    } u;
    u.c = c;
    __m128 w = _mm_cvtph_ps(u.v); // VCVTPH2PS
    float d;
    _mm_store_ss(&d, w);
    return (double)d;
}

#else

// Let's hope _Float16 is available

uint16_t hs_fp_ieee_floatToHalf(float x)
{
    union {
        _Float16 f;
        uint16_t u;
    } u;
    u.f = (_Float16)x;
    return u.u;
}

float hs_fp_ieee_halfToFloat(uint16_t x)
{
    union {
        _Float16 f;
        uint16_t u;
    } u;
    u.u = x;
    return (float)u.f;
}

uint16_t hs_fp_ieee_doubleToHalf(double x)
{
    union {
        _Float16 f;
        uint16_t u;
    } u;
    u.f = (_Float16)x;
    return u.u;
}

double hs_fp_ieee_halfToDouble(uint16_t x)
{
    union {
        _Float16 f;
        uint16_t u;
    } u;
    u.u = x;
    return (double)u.f;
}

#endif