存储非负浮点值

时间:2012-04-14 15:41:12

标签: c++ floating-point primitive-types

是否有一种使用现有float32float64格式存储非负浮点值的有效方法?

想象一下默认的float32行为允许消极/积极:

val = bytes.readFloat32();

如果不需要负值,是否可以允许更大的正值?

val = bytes.readFloat32() + 0xFFFFFFFF;

编辑:基本上当我知道我只存储正值时,浮点格式可以稍微修改一下,以便为相同数量的位提供更大的范围或精度。

EG。 float32格式定义为 1位用于标志, 8位用于展示, 23位用于分数

如果我不需要符号位,我们可以为expo 8位 24位如果分数为相同的32位提供更高的精度?

3 个答案:

答案 0 :(得分:3)

浮点数(float32float64)具有明确的符号位。浮点数不存在等值的无符号整数。

因此,没有简单的方法可以将正浮点数的范围加倍。

答案 1 :(得分:2)

不,不是免费的。

您可以使用其他数字表示以多种方式扩展范围/准确度。意图不明确,如果您希望floatdouble的范围和准确性使用其他数字表示(大小相等),性能通常会很差。

坚持使用floatdouble,除非性能/存储非常重要,您可以使用其他数字表示来表示您的价值(或更好!)。

答案 2 :(得分:0)

almost no support for unsigned float in hardware,因此您将没有现成的功能,但是通过将最低有效位存储在符号位中,您仍然可以具有相当高效的无符号浮点数。这样,您可以利用硬件可用的浮动支持而不是编写软件浮动解决方案。为此,您可以

  • 每次操作后手动对其进行操作

    这样,您需要对lsb(又称符号位)进行一些小的校正,例如,再加长1个除法步长,或者对加法使用1位加法器

  • ,或者通过更高的精度进行数学运算(如果可用)

    例如,如果类型为float,则可以在double中进行操作,然后在存储时回退到float

这是一个简单的PoC实现:

#include <cmath>
#include <cfenv>
#include <bit>
#include <type_traits>

// Does the math in double precision when hardware double is available
#define HAS_NATIVE_DOUBLE

class UFloat
{
public:
    // =========== Constructors ===========
    UFloat(float rhs = 0.0f) : value(rhs)
    {
        std::fesetround(FE_TOWARDZERO); // We'll round the value ourselves
#ifdef HAS_NATIVE_DOUBLE
        static_assert(sizeof(float) < sizeof(double));
#endif
    }
    UFloat(double d) : UFloat(0.0f)
    {
        if (d < 0)
            throw std::range_error("Value must be non-negative!");
        uint64_t dbits = std::bit_cast<uint64_t>(d);
        bool lsb = dbits & lsbMask;
        dbits &= ~lsbMask; // turn off the lsb
        d = std::bit_cast<double>(dbits);
        value = lsb ? -(float)d : (float)d;
    }
    UFloat(const UFloat &rhs) : UFloat(rhs.value) {}

    // =========== Operators ===========
    UFloat &operator+=(const UFloat &rhs)
    {
#ifdef HAS_NATIVE_DOUBLE
        // Calculate in higher precision then round back
        setValue((double)value + rhs.value);
#else
        // Calculate the least significant bit manually
        
        bool lhsLsb = std::signbit(value);
        bool rhsLsb = std::signbit(rhs.value);
        // Clear the sign bit to get the higher significant bits then get the sum
        value = std::abs(value);
        value += std::abs(rhs.value);
        if (std::isfinite(value))
        {
            if (lhsLsb ^ rhsLsb) // Only one of the 2 least significant bits is 1
            {
                // The sum's least significant bit is 1, so we'll set its sign bit
                value = -value;
            }
            else if (lhsLsb)
            {
                // Both least significant bits are 1s, so add the carry to the next bit
                // The least significant bit of the sum is 0, so we don't change the sign bit
                value = std::nextafter(value, INFINITY);
            }
        }
#endif
        return *this;
    }

    UFloat &operator*=(const UFloat &rhs)
    {
#ifdef HAS_NATIVE_DOUBLE
        // Calculate in higher precision then round back
        setValue((double)value * rhs.value);
#else
        // Calculate the least significant bit manually
    
        bool lhsLsb = std::signbit(value);
        bool rhsLsb = std::signbit(rhs.value);

        // Clear the sign bit to get the higher significant bits then get the product
        float lhsMsbs = std::abs(value);
        float rhsMsbs = std::abs(rhs.value);

        // Suppose we have X.xPm with X: the high significant bits, x: the least significant one
        // and m: the exponent. Same to Y.yPn
        // X.xPm * Y.yPn = (X + 0.x)*2^m * (Y + 0.y)*2^n
        //               = (X + x/2)*2^m * (Y + y/2)*2^n
        //               = (X*Y + X*y/2 + Y*x/2 + x*y/4)*2^(m + n)
        value = lhsMsbs * rhsMsbs; // X*Y
        if (std::isfinite(value))
        {
            uint32_t rhsMsbsBits = std::bit_cast<uint32_t>(rhsMsb);
            value += rhsMsbs*lhsLsb / 2; // X*y/2
            
            uint32_t lhsMsbsBits = std::bit_cast<uint32_t>(lhsMsbs);
            value += lhsMsbs*rhsLsb / 2; // Y*x/2
            
            int lsb = (rhsMsbsBits | lhsMsbsBits) & 1; // the product's lsb
            lsb += lhsLsb & rhsLsb;
            if (lsb & 1)
                value = -value; // set the lsb
            if (lsb > 1)    // carry to the next bit
                value = std::nextafter(value, INFINITY);
        }
#endif

        return *this;
    }
    
    UFloat &operator/=(const UFloat &rhs)
    {
#ifdef HAS_NATIVE_DOUBLE
        // Calculate in higher precision then round back
        setValue((double)value / rhs.value);
#else
        // Calculate the least significant bit manually
        // Do just one more step of long division, since we only have 1 bit left to divide

        throw std::runtime_error("Not Implemented yet!");
#endif

        return *this;
    }

    double getUnsignedValue() const
    {
        if (!std::signbit(value))
        {
            return (double)value;
        }
        else
        {
            double result = std::abs(value);
            uint64_t doubleValue;
            memcpy(&doubleValue, &result, sizeof result);
            doubleValue |= lsbMask; // turn on the least significant bit
            memcpy(&result, &doubleValue, sizeof result);
            return result;
        }
    }
    
private:
    // The unsigned float value, with the least significant bit being stored in the sign bit
    float value;
    
    // the first bit after the normal mantissa bits
    static const uint64_t lsbMask = 1ULL << (DBL_MANT_DIG - FLT_MANT_DIG - 1);
    void setValue(double d)
    {
        auto bits = std::bit_cast<std::uint64_t>(d); // get the bit pattern of the double value
        bool lsb = bits & lsbMask;
        bits &= ~lsbMask;                            // turn off the lsb to avoid rounding when converting to float
        d = std::bit_cast<double>(bits);
        value = (float)d;
        if (lsb)
            value = -value;
    }
}

可能需要更多调整才能获得正确的lsb

无论哪种方式,您都需要比平常更多的操作,因此这仅对关注缓存占用空间的大型阵列有用。在那种情况下,我建议将此仅用作存储格式,就像在大多数当前体系结构上如何对待FP16一样:只有加载/存储指令可以扩展为{{1 }}或float并转换回来。所有算术运算仅在doublefloat中完成

因此,无符号浮点数应仅存在于内存中,并且在加载时将被解码为完整的double。这样,您就可以处理本机double类型,无需在每个运算符之后进行更正