没看出来有什么用
你这段内容是在讲 定点数(Fixed-Point Number) 的结构和表示方法。让我帮你详细解释一下:
定点数的结构(Anatomy of a 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)
比如说:- 8 位整数(
I
) - 8 位小数(
F
)
这个定点数用 16 位二进制数来表示,其中高 8 位是整数部分,低 8 位是小数部分。
具体数值举例
定点数的值计算公式是:
Value = Integer part + Fractional part \text{Value} = \text{Integer part} + \text{Fractional part} Value=Integer part+Fractional part
举例:
- 最大整数部分: 2 7 + 2 6 + ⋯ + 2 0 = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255 2^7 + 2^6 + \cdots + 2^0 = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1 = 255 27+26+⋯+20=128+64+32+16+8+4+2+1=255
- 最大小数部分: 1 / 2 + 1 / 4 + 1 / 8 + ⋯ + 1 / 256 = 1 − 1 / 256 = 255 / 256 ≈ 0.99609375 1/2 + 1/4 + 1/8 + \cdots + 1/256 = 1 - 1/256 = 255/256 \approx 0.99609375 1/2+1/4+1/8+⋯+1/256=1−1/256=255/256≈0.99609375
因此整个数最大值是:
255 + 255 256 = 255.99609375 255 + \frac{255}{256} = 255.99609375 255+256255=255.99609375
数值换算
定点数通常存储为整数,但你要理解它实际表示的值,需要除以一个缩放因子:
- 比如这里小数部分是 8 位,则缩放因子是 2 8 = 256 2^8 = 256 28=256。
- 存储值是整数,比如最大是 65535。
因此:
65535 256 = 255.99609375 \frac{65535}{256} = 255.99609375 25665535=255.99609375
正好对应上述最大值。
总结
- 定点数用固定数量的整数位和小数位表示数值。
- 实际数值 = 存储的整数 / 2 fractional_bits 2^{\text{fractional\_bits}} 2fractional_bits
- 这种表示方式常用在嵌入式系统、DSP 及不方便用浮点数计算的硬件中。
fixed_point.h
(版本0)示例代码实现了一个简单的 定点数(u8.8格式) 类型及其基本转换和运算功能。理解和批评点都非常有价值,我帮你详细拆解和解释一下:
代码功能简介
cpp
#include <cinttypes>
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; // 定点乘法,结果缩放回来
}
代码存在的问题(Criticisms)
- 类型安全(Type Safety)
float
和u8_8
(定点数)类型虽然底层都是数值,但含义完全不同,混用容易出错。- 建议封装成专用类,防止误用。
- 通用性(Generality)
- 目前只支持
u8.8
,即8位整数,8位小数的定点格式。 - 不能灵活支持其他定点格式,比如
u16.16
或带符号的定点数。
- 目前只支持
- 易用性(Usability)
- 目前只有函数实现加法和乘法,缺少运算符重载,不方便写表达式。
- 例如没有重载
operator+
、operator*
等。
- 溢出安全(Overflow Safety)
- 加法和乘法都没有检测溢出。
- 比如
255 * 255
可能溢出uint16_t
,结果不可预测。
- 精度和舍入(Fidelity)
- 乘法除以256时没有任何舍入处理,默认向下取整,可能造成累计误差。
- 更好的实现会有舍入、四舍五入等策略。
- 类型提升和预测性(Predictability)
uint8_8
是uint16_t
,但乘法中转成了uint32_t
以避免溢出,后续再除以256。- 这种隐式类型提升不易察觉,代码复杂度提高。
- 可移植性(Portability)
- 虽然
uint16_t
在多数平台是16位无符号整数,但标准并不保证所有平台行为完全一致。 - 对于跨平台定点数库,需要特别注意整型大小和溢出行为。
- 虽然
总结和建议
- 你这段代码是定点数的最简版本,很适合作为入门学习示例。
- 若要用在实际项目中,需要改进:
- 用
class FixedPoint
把底层类型封装起来,增加类型安全。 - 支持不同的定点格式,通过模板参数来指定整数和小数位数。
- 重载算术运算符。
- 添加溢出检测或使用更大类型中间计算。
- 实现舍入策略,保证精度。
- 用
- 这样才能更健壮、更易用、更可移植。
对定点数实现中"整数不会遇到的问题"做进一步批评,并提出一种模块化解决方案的假设和建议。下面帮你总结和补充理解:
Criticisms (针对定点数,但整数一般不会有的主要问题)
- 类型安全 (Type Safety)
浮点数(float)和定点数表示不同语义,不能随便混用,导致容易出错。
--- 纯整数通常语义单一,混淆少。 - 通用性 (Generality)
只支持特定定点格式(如 u8.8),缺乏灵活性。
--- 整数类型可选性多,且标准明确。 - 易用性 (Usability)
缺少算术运算符重载,表达式难写。
--- 整数内建运算符齐全。 - 溢出安全 (Overflow Safety)
乘法加法易溢出且无检测。
--- 整数运算可能溢出,但已有方案可检测。 - 精度问题 (Fidelity)
乘除操作舍入倾向零或负无穷,累积误差大。
--- 整数无小数问题。 - 预测性 (Predictability)
类型隐式变化(如int提升),可能导致意外行为。
--- 整数提升规则有标准定义但仍复杂。 - 可移植性 (Portability)
int
等类型大小和行为平台不同,移植难。
--- 整数类型标准化程度有限。
你的假设(Hypothesis)
针对 C++ 中内建定点数类型存在的问题,最好是逐个问题用独立的"字面量类模板"(literal class template)来解决。
- 每个问题对应一个专门的解决类模板。
- 比如解决溢出检测的
checked_integer<>
, - 解决结果宽度的
widening_integer<>
, - 解决舍入问题的
rounded_integer<>
, - 解决子单位精度的
fixed_point<>
。
- 比如解决溢出检测的
- 这些模板可以和内建类型组合产生新的数值类型。
- 多个模板可以组合,形成一个同时解决多个问题的类型。
- 所有操作在零运行时成本(zero runtime cost)下完成------依赖编译期机制和内联。
- 编译时间成本也尽量最小化。
具体建议的模板示例
checked_integer<>
:带溢出检测的整数,运行时溢出时抛异常或断言。widening_integer<>
:运算时结果自动用更大类型存储,避免溢出。rounded_integer<>
:运算和浮点转整数时进行合理舍入。fixed_point<>
:支持定点格式,带整数和小数位数模板参数。
总结
你的观点是把定点数设计拆成几个可组合、专注单一职责的模板,这样比写一个大而全的"魔法类"更灵活、可维护,也更高效。且可以逐步解决定点数的各种棘手问题。
sg14::fixed_point<>
是 C++ 标准化努力中的一个定点数模板类,相关提案是 P0037,作者 John McFarlane 维护的库在 GitHub 上。
sg14::fixed_point 简介
-
命名空间 :
sg14
(即 Standard Group 14,C++ 标准库扩展小组) -
模板参数 :
Rep
:底层整数类型(默认是int
)Exponent
:定点数的小数位的指数,负数表示小数位数,比如-8
表示 8 位小数
-
定义示例 :
cppnamespace sg14 { template<class Rep = int, int Exponent = 0> class fixed_point; }
-
使用示例 :
cpp#include <sg14/fixed_point.h> using u8_8 = sg14::fixed_point<uint16_t, -8>;
这里的
u8_8
就是用 16 位无符号整数(uint16_t
)作为底层存储,且有 8 位小数的定点数类型。
优势和特点
- 类型安全:底层整数类型与小数位数都明确,避免误用。
- 泛型设计:模板化,可以灵活选择底层整数和小数精度。
- 算术运算符支持:重载了算术、比较等运算符,方便使用。
- 溢出和舍入 :通常结合其他辅助模板(如
sg14::overflow_handler
)来处理。 - 兼容性:设计为标准库扩展,有望未来纳入C++标准。
- 零开销抽象:编译期常量参数和内联函数确保运行时无额外开销。
进一步行动
- 你可以看看这个库的源码和文档,尝试在项目中使用它。
- 可以对比自己写的定点数实现,看看它解决了哪些"批评点"。
- 如果你想,我可以帮你写几个
sg14::fixed_point
的小示例,演示基本的用法和运算。
这部分内容讨论了定点数库设计中非常关键的"乘法问题"和相关的算术实现、类型安全、扩展性等。
乘法问题(Multiply Problem)
乘法操作的返回类型(decltype
)设计:
- 截断(Truncate)策略 :
- 丢弃低位 :
- 适合
make_fixed<0, N>
(纯小数位) - 不适合
make_fixed<N, 0>
(纯整数位)
- 适合
- 丢弃高位 :
- 适合
make_fixed<N, 0>
- 不适合
make_fixed<0, N>
- 适合
- 匹配操作数 :
- 结果用
fixed_point<decltype(R()*R()), E>
,即乘积类型的底层整数类型和相同的指数,但会造成溢出风险。
- 结果用
- 丢弃低位 :
- 扩展(Widen)策略 :
- 乘法结果用更宽的整数类型表示,避免溢出风险。
- 产生新类型,编译期需要计算位宽,可能影响编译速度。
- 但赋值回窄类型时会截断,仍需注意。
算术函数示例
cpp
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; // 结果正确,但更易溢出
组合(Composition)
- 定义极大位数的定点类型,如 400 位整数 + 400 位小数,底层用多精度库(Boost Multiprecision)存储。
- 计算大数如 googol (10^100):
cpp
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"
Elastication™(弹性整数)
- 弹性整数根据运算自动扩展位宽,避免溢出。
cpp
auto a = elastic_integer<6, int8_t>{63};
auto aa = a*a;
static_assert(is_same<decltype(aa), elastic_integer<12, int8_t>>::value, "");
- 乘法后类型自动升级成12位宽的弹性整数,大小也变为2字节。
Elastication + fixed_point
- 结合弹性整数和定点数,自动扩展精度且安全。
cpp
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, "");
安全性(Safety)
- 用
boost::numeric::safe<int>
等安全类型来防止溢出。
cpp
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; // 输出异常信息
}
语言特性相关
- C++11/14:
constexpr
,auto
,decltype
,用户自定义字面量 - C++17:模板参数推导
- 未来(C++20+?):concepts 和更多语言支持,可能会进一步提升定点数的易用性和安全性。