【问题标题】:Implementing a half precision floating point number in C++在 C++ 中实现半精度浮点数
【发布时间】:2013-08-04 14:23:22
【问题描述】:

我正在尝试实现一个简单的半精度浮点类型,完全用于存储目的(没有算术,隐式转换为双精度),但我得到了奇怪的行为。 Half 在 -0.5 和 0.5 之间得到完全错误的值。我还得到一个令人讨厌的值“偏移”,例如 0.8 被解码为 0.7998。

我对 C++ 很陌生,所以如果您能指出我的错误并帮助我稍微提高准确性,我会非常高兴。我也很好奇这个解决方案的便携性。谢谢!

这是输出 - 双精度值和一半的实际解码值:

-1 -1
-0.9 -0.899902
-0.8 -0.799805
-0.7 -0.699951
-0.6 -0.599854
-0.5 -0.5
-0.4 -26208
-0.3 -19656
-0.2 -13104
-0.1 -6552
-1.38778e-16 -2560
0.1 6552
0.2 13104
0.3 19656
0.4 26208
0.5 32760
0.6 0.599854
0.7 0.699951
0.8 0.799805
0.9 0.899902

这是目前为止的代码:

#include <stdint.h>
#include <cmath>
#include <iostream>

using namespace std;

#define EXP 4
#define SIG 11

double normalizeS(uint v) {
    return (0.5f * v / 2048 + 0.5f);
}

uint normalizeP(double v) {
    return (uint)(2048 * (v - 0.5f) / 0.5f);
}

class Half {

    struct Data {
        unsigned short sign : 1;
        unsigned short exponent : EXP;
        unsigned short significant : SIG;
    };

public:
    Half() {}
    Half(double d) { loadFromFloat(d); }

    Half & operator = (long double d) {
        loadFromFloat(d);
        return *this;
    }

    operator double() {
        long double sig = normalizeS(_d.significant);
        if (_d.sign) sig = -sig;
        return ldexp(sig, _d.exponent /*+ 1*/);
    }

private:
    void loadFromFloat(long double f) {
        long double v;
        int exp;
        v = frexp(f, &exp);
        v < 0 ? _d.sign = 1 : _d.sign = 0;
        _d.exponent = exp/* - 1*/;
        _d.significant = normalizeP(fabs(v));
    }

    Data _d;
};

int main() {

        Half a[255];

        double d = -1;

        for (int i = 0; i < 20; ++i) {
            a[i] = d;
            cout << d << " " << a[i] << endl;
            d += 0.1;
        }
}

【问题讨论】:

  • 这里有一个类似的问题:stackoverflow.com/questions/3316130/…
  • 尝试将 0.8 转换为二进制并仅使用您拥有的位数进行存储。而不是尝试将其转换回十进制并查看结果。如果您只有 2 个十进制位,则只能使用 1/2 和 1/4 从而尝试存储例如.8 将表示为 1/2+1/4 = .75 这比 1 更接近 .8 但你仍然拥有你所说的 offset

标签: c++ implementation portability bit-fields precision


【解决方案1】:

我最终得到了一个非常简单(真的很天真)的解决方案,能够表示我需要的范围内的每个值:0 - 64,精度为 0.001。

由于想法是将其用于存储,这实际上更好,因为它允许在double 之间进行转换,而不会损失任何分辨率。它也更快。它实际上以具有更好的最小步长的名义失去了一些分辨率(小于 16 位),因此它可以表示任何输入值而无需近似 - 所以在这种情况下,LESS 是 MORE。对浮动组件使用完整的 2^10 分辨率会导致无法准确表示十进制值的奇数步长。

class Half {
public:
    Half() {}
    Half(const double d) { load(d); }
    operator double() const { return _d.i + ((double)_d.f / 1000); }
private:
    struct Data {
        unsigned short i : 6;
        unsigned short f : 10;
    };
    void load(const double d) {
        int i = d;
        _d.i = i;
        _d.f = round((d - i) * 1000);
    }
    Data _d;
};

【讨论】:

    【解决方案2】:

    上一个解决方案错误...抱歉...

    尝试将指数更改为有符号...它在这里工作。

    问题是当指数变为负数时,当值

    【讨论】:

    • 使用上面的建议。
    • 可以,例如,0.25 = 1 x 2^(-2)。小于 0.5 到 -0.5 的数字为负数
    • 啊,是的,我明白了,使用有符号指数有助于修复 -0.5 到 0.5 之间的值的错误,但我仍然有烦人的偏移和精度损失。
    • ted 的注释解释了偏移的原因。如果您需要减少此“错误”,则需要将模式位添加到“SIG”。
    • 实际上,将指数切换为signed 修复了小值,但打破了大值,切换到signed 突然1000 被解码为0.0152588...
    猜你喜欢
    • 2019-11-10
    • 2019-01-19
    • 2020-02-03
    • 1970-01-01
    • 2011-09-03
    • 2011-10-14
    • 1970-01-01
    • 2011-08-06
    • 2011-11-17
    相关资源
    最近更新 更多