我正在尝试实现一个简单的半精度浮点类型,完全用于存储目的(没有算术,隐式转换为双精度(,但我得到了奇怪的行为。我在 -0.5 和 0.5 之间的Half
得到完全错误的值。此外,我得到了一个令人讨厌的值"偏移量",例如 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;
}
}
我最终得到了一个非常简单(真的很天真(的解决方案,能够表示我需要的范围中的每个值:0 - 64,精度为 0.001。
由于这个想法是将其用于存储,这实际上更好,因为它允许在double
之间进行转换,而不会丢失任何分辨率。它也更快。它实际上以具有更好的最小步长的名义丢失了一些分辨率(小于 16 位(,因此它可以表示任何输入值而无需近似 - 所以在这种情况下,少即是多。对浮动组件使用完整的 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;
};
最后一个解决方案错误...不好意思。。。
尝试将公开对象更改为已签名...它在这里起作用。
问题是,当暴露变为负数时,当值<0.5 时,您将暴露对象保存为正数,当 abs(val(<0.5 时,是导致数字变大的问题。