CppCon 2016 学习:fixed_point Library

没看出来有什么用

你这段内容是在讲 定点数(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)

  1. 类型安全(Type Safety)
    • floatu8_8(定点数)类型虽然底层都是数值,但含义完全不同,混用容易出错。
    • 建议封装成专用类,防止误用。
  2. 通用性(Generality)
    • 目前只支持 u8.8,即8位整数,8位小数的定点格式。
    • 不能灵活支持其他定点格式,比如 u16.16 或带符号的定点数。
  3. 易用性(Usability)
    • 目前只有函数实现加法和乘法,缺少运算符重载,不方便写表达式。
    • 例如没有重载 operator+operator* 等。
  4. 溢出安全(Overflow Safety)
    • 加法和乘法都没有检测溢出。
    • 比如 255 * 255 可能溢出 uint16_t,结果不可预测。
  5. 精度和舍入(Fidelity)
    • 乘法除以256时没有任何舍入处理,默认向下取整,可能造成累计误差。
    • 更好的实现会有舍入、四舍五入等策略。
  6. 类型提升和预测性(Predictability)
    • uint8_8uint16_t,但乘法中转成了 uint32_t 以避免溢出,后续再除以256。
    • 这种隐式类型提升不易察觉,代码复杂度提高。
  7. 可移植性(Portability)
    • 虽然 uint16_t 在多数平台是16位无符号整数,但标准并不保证所有平台行为完全一致。
    • 对于跨平台定点数库,需要特别注意整型大小和溢出行为。

总结和建议

  • 你这段代码是定点数的最简版本,很适合作为入门学习示例。
  • 若要用在实际项目中,需要改进:
    • class FixedPoint 把底层类型封装起来,增加类型安全。
    • 支持不同的定点格式,通过模板参数来指定整数和小数位数。
    • 重载算术运算符。
    • 添加溢出检测或使用更大类型中间计算。
    • 实现舍入策略,保证精度。
  • 这样才能更健壮、更易用、更可移植。

对定点数实现中"整数不会遇到的问题"做进一步批评,并提出一种模块化解决方案的假设和建议。下面帮你总结和补充理解:

Criticisms (针对定点数,但整数一般不会有的主要问题)

  1. 类型安全 (Type Safety)
    浮点数(float)和定点数表示不同语义,不能随便混用,导致容易出错。
    --- 纯整数通常语义单一,混淆少。
  2. 通用性 (Generality)
    只支持特定定点格式(如 u8.8),缺乏灵活性。
    --- 整数类型可选性多,且标准明确。
  3. 易用性 (Usability)
    缺少算术运算符重载,表达式难写。
    --- 整数内建运算符齐全。
  4. 溢出安全 (Overflow Safety)
    乘法加法易溢出且无检测。
    --- 整数运算可能溢出,但已有方案可检测。
  5. 精度问题 (Fidelity)
    乘除操作舍入倾向零或负无穷,累积误差大。
    --- 整数无小数问题。
  6. 预测性 (Predictability)
    类型隐式变化(如int提升),可能导致意外行为。
    --- 整数提升规则有标准定义但仍复杂。
  7. 可移植性 (Portability)
    int等类型大小和行为平台不同,移植难。
    --- 整数类型标准化程度有限。

你的假设(Hypothesis)

针对 C++ 中内建定点数类型存在的问题,最好是逐个问题用独立的"字面量类模板"(literal class template)来解决

  1. 每个问题对应一个专门的解决类模板。
    • 比如解决溢出检测的 checked_integer<>
    • 解决结果宽度的 widening_integer<>
    • 解决舍入问题的 rounded_integer<>
    • 解决子单位精度的 fixed_point<>
  2. 这些模板可以和内建类型组合产生新的数值类型。
  3. 多个模板可以组合,形成一个同时解决多个问题的类型。
  4. 所有操作在零运行时成本(zero runtime cost)下完成------依赖编译期机制和内联。
  5. 编译时间成本也尽量最小化。

具体建议的模板示例

  • 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 位小数
  • 定义示例

    cpp 复制代码
    namespace 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:constexprautodecltype,用户自定义字面量
  • C++17:模板参数推导
  • 未来(C++20+?):concepts 和更多语言支持,可能会进一步提升定点数的易用性和安全性。
相关推荐
虾球xz4 小时前
CppCon 2016 学习:GAME ENGINE USING C++11
大数据·开发语言·c++·学习
Jet45054 小时前
第100+42步 ChatGPT学习:R语言实现阈值调整
开发语言·学习·chatgpt·r语言
希希不嘻嘻~傻希希4 小时前
CSS 字体与文本样式笔记
开发语言·前端·javascript·css·ecmascript
HaiQinyanAN5 小时前
【学习笔记】nlohmannjson&&cjson
c++·笔记·学习·json
C语言小火车5 小时前
【C语言】银行账户管理系统丨源码+解析
c语言·c++·算法·课程设计
寄思~6 小时前
Python学习笔记:错误和异常处理
开发语言·笔记·python·学习
啊哈哈哈哈哈啊哈哈6 小时前
G2打卡——人脸图像生成
pytorch·学习·gan
clmm1236 小时前
Java动态生成Nginx服务配置
java·开发语言·nginx
茯苓gao6 小时前
小孙学变频学习笔记(二)变频器的逆变电路
笔记·嵌入式硬件·学习