你这段内容是在讲 定点数(Fixed-Point Number) 的结构和表示方法。让我帮你详细解释一下:
这里的符号排列表示一个定点数的位分布:
I I I I I I I I F F F F F F F F
I
代表整数部分的位(Integer bits)F
代表小数部分的位(Fractional bits)I
)F
)定点数的值计算公式是:
Value = Integer part + Fractional part \text{Value} = \text{Integer part} + \text{Fractional part} Value=Integer part+Fractional part
举例:
定点数通常存储为整数,但你要理解它实际表示的值,需要除以一个缩放因子:
fixed_point.h
(版本0)示例代码实现了一个简单的 定点数(u8.8格式) 类型及其基本转换和运算功能。理解和批评点都非常有价值,我帮你详细拆解和解释一下:#include
using u8_8 = std::uint16_t; // 16位无符号整数表示u8.8定点数
constexpr u8_8 float_to_fixed(float f)
{
return f * 256; // 把float转成定点格式:乘以2^8
}
constexpr float fixed_to_float(u8_8 i)
{
return i / 256.f; // 把定点格式转成float:除以2^8
}
constexpr u8_8 add(u8_8 a, u8_8 b)
{
return a + b; // 定点数加法,直接加整数值即可
}
constexpr u8_8 multiply(u8_8 a, u8_8 b)
{
return (uint32_t(a) * uint32_t(b)) / 256; // 定点乘法,结果缩放回来
}
float
和 u8_8
(定点数)类型虽然底层都是数值,但含义完全不同,混用容易出错。u8.8
,即8位整数,8位小数的定点格式。u16.16
或带符号的定点数。operator+
、operator*
等。255 * 255
可能溢出 uint16_t
,结果不可预测。uint8_8
是 uint16_t
,但乘法中转成了 uint32_t
以避免溢出,后续再除以256。uint16_t
在多数平台是16位无符号整数,但标准并不保证所有平台行为完全一致。class FixedPoint
把底层类型封装起来,增加类型安全。int
等类型大小和行为平台不同,移植难。针对 C++ 中内建定点数类型存在的问题,最好是逐个问题用独立的“字面量类模板”(literal class template)来解决。
checked_integer<>
,widening_integer<>
,rounded_integer<>
,fixed_point<>
。checked_integer<>
:带溢出检测的整数,运行时溢出时抛异常或断言。widening_integer<>
:运算时结果自动用更大类型存储,避免溢出。rounded_integer<>
:运算和浮点转整数时进行合理舍入。fixed_point<>
:支持定点格式,带整数和小数位数模板参数。你的观点是把定点数设计拆成几个可组合、专注单一职责的模板,这样比写一个大而全的“魔法类”更灵活、可维护,也更高效。且可以逐步解决定点数的各种棘手问题。
sg14::fixed_point<>
是 C++ 标准化努力中的一个定点数模板类,相关提案是 P0037,作者 John McFarlane 维护的库在 GitHub 上。sg14
(即 Standard Group 14,C++ 标准库扩展小组)Rep
:底层整数类型(默认是 int
)Exponent
:定点数的小数位的指数,负数表示小数位数,比如 -8
表示 8 位小数namespace sg14 {
template<class Rep = int, int Exponent = 0>
class fixed_point;
}
#include
using u8_8 = sg14::fixed_point<uint16_t, -8>;
这里的 u8_8
就是用 16 位无符号整数(uint16_t
)作为底层存储,且有 8 位小数的定点数类型。sg14::overflow_handler
)来处理。sg14::fixed_point
的小示例,演示基本的用法和运算。decltype
)设计:make_fixed<0, N>
(纯小数位)make_fixed
(纯整数位)make_fixed
make_fixed<0, N>
fixed_point
,即乘积类型的底层整数类型和相同的指数,但会造成溢出风险。auto x = fixed_point<uint8_t, -4>{15.9375};
// x * x = 254.00390625,但 uint8_t 存不下,会溢出
cout << fixed_point<uint8_t, -4>{x*x} << endl; // 输出 "14",错误结果
// 乘法操作符会自动扩展类型
auto xx = x*x;
static_assert(is_same<decltype(xx), fixed_point<uint16_t, -8>>::value, "");
cout << setprecision(12) << xx << endl; // 正确输出 254.00390625
// 也可以用命名函数来调用乘法
auto named_xx = multiply(x, x);
static_assert(is_same<decltype(named_xx), fixed_point<int, -8>>::value, "");
cout << named_xx << endl; // 结果正确,但更易溢出
using big_number = make_ufixed<400, 400, boost::multiprecision::uint128_t>;
auto googol = big_number{1};
for (auto zeros = 0; zeros != 100; ++zeros)
googol *= 10;
cout << googol << endl; // 输出 "1e+100"
cout << big_number{1} / googol << endl; // 近似输出 "1e-100"
auto a = elastic_integer<6, int8_t>{63};
auto aa = a*a;
static_assert(is_same<decltype(aa), elastic_integer<12, int8_t>>::value, "");
template<int IntegerDigits, int FractionalDigits, typename Archetype>
using elastic = fixed_point<elastic_integer<IntegerDigits+FractionalDigits, Archetype>, -FractionalDigits>;
auto b = elastic<4, 28, unsigned>{15.9375};
auto bb = b*b;
cout << bb << endl; // "254.00390625"
static_assert(is_same<decltype(bb), elastic<8, 56, unsigned>>::value, "");
boost::numeric::safe
等安全类型来防止溢出。using safe_byte = make_fixed<3, 4, boost::numeric::safe<int>>;
try {
auto a = safe_byte{-8};
cout << a << endl; // 输出 "-8"
auto b = safe_byte{10}; // 超出范围
cout << b << endl;
} catch(std::range_error e) {
cout << e.what() << endl; // 输出异常信息
}
constexpr
,auto
,decltype
,用户自定义字面量