溢出与舍入

共 18 题
📑 题目列表 18 题
#
★★★

1. 把十进制 0.1 用 IEEE 754 双精度表示,写出近似值的十六进制位模式?

请把十进制 0.1 用 IEEE 754 双精度表示,并写出其近似值的十六进制位模式?

  • 双精度格式
  • 0.1 的舍入位模式
  • 双精度指数偏置 1023 与存储指数

0.1 在二进制中约等于 0.0001100110011001100110011001100110011001100110011001101…(无限循环)。双精度为 1 位符号 + 11 位指数 + 52 位尾数。0.1 的符号为 0,指数为 -4(规格化后 1.1001100…×2^-4),存储指数 = -4 + 1023 = 1019 = 0x3FB。尾数取 52 位并舍入后,0.1 的双精度位模式为 0x3FB999999999999A(十六进制 8 字节)。该值略大于真实 0.1。

0.1 无法精确表示,双精度舍入到最近的 52 位尾数,得到 0x3FB999999999999A。索引字节 0x3F 给出符号与指数,B9 开始是尾数。这是面试中 0.1 精度问题的根源。

#
★★★

2. 给定浮点 1.5 与 2.5 的平均,在四种默认舍入模式下的结果是否一致?

给定浮点 1.5 与 2.5 的平均,在 IEEE 754 四种默认舍入模式下结果是否一致?

  • 四种舍入模式
  • 精确值舍入
  • 精确可表示值无需舍入

(1.5 + 2.5) / 2 = 2.0,这是一个二进制精确可表示的值(2.0 = 1.0×2^1)。因为结果是精确值,不存在舍入需求,四种舍入模式(向最近偶数、向零、向下、向上)的结果都一致,都是 2.0。只有当结果落在两个相邻可表示数之间时,不同舍入模式才可能给出不同结果。

舍入模式只在结果无法精确表示时才起作用。2.0 是精确的,所以四种模式无差异。测试舍入模式应选择需要舍入的中间值。

#
★★★

3. 解释 IEEE 754 中 NaN 与 Inf 的存在意义?

请解释 IEEE 754 中 NaN(非数值)与 Inf(无穷大)存在的意义?

  • NaN 与 Inf 的用途
  • 特殊值的表示
  • 指数域全 1 的特殊值判定

Inf(无穷大,指数域全 1 且尾数全 0)用于表示溢出结果,如正数除以 0、超过最大可表示数,使运算能继续传播而不崩溃。NaN(指数域全 1 且尾数非 0)用于表示无意义或未定义的结果,如 0/0、无穷减无穷、对负数开平方。NaN 用于传播错误信号,且比较时 NaN 不等于任何值(包括自身)。Inf 的存在让浮点运算具备饱和与符号保持能力。

这两个特殊值使浮点运算在异常情况仍能给出可传播的结果,而不是中止程序。判别时用 isinf、isnan,或判断指数域全 1。

#
★★★

4. 解释 IEEE 754 四种默认舍入模式(向最近、向下、向上、向零)的差异?

请解释 IEEE 754 四种默认舍入模式(向最近、向下、向上、向零)的差异?

  • 四种舍入模式语义
  • 向最近偶数
  • 向最近偶数避免系统性统计偏差

四种模式:向最近偶数(round to nearest even),结果取最接近的值,恰好平分时取偶数尾数,是默认模式;向零(round toward zero),截断绝对值,靠近 0;向下(round down)取不超过结果的最大值(向负无穷);向上(round up)取不小于结果的最小值(向正无穷)。向最近偶数误差最小,且不会产生统计偏差,因而被选为默认。

向最近偶数在平分时选偶数尾数,避免向最近四舍五入在求和时引入系统性偏差。银行家舍入即其十进制版本。其他三种模式用于严格区间估计。

#
★★★

5. 解释浮点 1.0 与 -1.0 相加得到 0(正零)的位模式是什么?

请解释浮点 1.0 与 -1.0 相加得到 0(正零)的位模式是什么?

  • 正负零
  • 浮点加法结果 0 的符号
  • 正零与负零仅符号位不同

1.0 与 -1.0 相加,数学结果为 0。IEEE 754 规定,符号相反、绝对值相等的两个数相加,结果为正零(+0),除非舍入模式为向下(round toward -inf)时结果为负零。所以默认舍入模式下结果是 +0,其位模式为符号位 0、指数全 0、尾数全 0,即 32 位 0x00000000(单精度)。

+0 与 -0 数值相等,但位模式不同(符号位不同)。加法结果符号由舍入模式和操作数决定。默认最近偶数下结果为 +0。

#
★★★

6. 浮点下溢(underflow)与次正规数(subnormal)中,单精度最接近 0 的正规数是多少,渐进式下溢如何牺牲精度换取范围?

请解释浮点下溢与次正规数,单精度最接近 0 的正规数是多少?渐进式下溢如何牺牲精度换范围?

  • 下溢与次正规数
  • 渐进式下溢
  • 最小正规数 2^-126 与最小次正规数 2^-149 的对比

单精度最小正规数为 2^-126 ≈ 1.18×10^-38。若结果小于该值但大于 0,发生下溢。IEEE 754 采用渐进式下溢:指数域为 0 时使用次正规数(subnormal),去掉隐含的 1,尾数从 0 开始,可表示到最小次正规数 2^-149 ≈ 1.4×10^-45。次正规数牺牲有效位(精度),因为隐含位被去掉,尾数有效位数减少,但换来了更接近 0 的范围,避免突然下溢到 0。

渐进式下溢使下溢是平滑的,不是截断到 0。次正规数精度降低但范围扩大,众多计算在小数相加时能保持相对误差不至于失控。

#
★★

7. 在 SQL 中 SUM 大量 DECIMAL 列可能溢出?用何种类型与策略规避?

在 SQL 中 SUM 大量 DECIMAL 列可能溢出,应该用什么类型与策略规避?

  • DECIMAL 精度限制
  • 溢出规避策略
  • 精度与标度设计预留余量

DECIMAL 有固定精度和小数位数,且 SUM 的精度由输入列决定,累加大量记录可能超过该精度导致溢出或报错。规避策略:使用更大精度的 DECIMAL 类型(如 DECIMAL(38, 2) 的更大标度),或显式将参与累加的列 cast 为更高精度;分解为分桶求和(GROUP BY 后汇总);或使用 NUMERIC 大精度类型;必要时用 double 或定期折算。关键是在建表时预留足够精度,并对 SUM 结果 cast 到更高精度。

溢出源于固定精度。通过 CAST 提升精度(如 CAST(col AS DECIMAL(38,10)))或分桶聚合,可避免溢出。审计精度设计与测试大数据量是关键。

#
★★

8. 给定有符号 char x = 127; ++x; 求 x 的十进制值与产生原因?

给定有符号 char x = 127,执行 ++x 后,求 x 的十进制值及产生原因?

  • 有符号溢出
  • char 类型范围
  • 符号位翻转引起回绕

有符号 char 范围是 [-128, 127]。127 是最大值,++x 使 01111111 加 1 得 10000000,即 -128。所以 x 变为 -128。在 C 中,有符号整数溢出是未定义行为(UB),但常见实现(二进制补码)会回绕到最小值。结果 -128 是回绕得到,而非定义行为。

01111111 + 1 = 10000000,符号位翻转,产生溢出。虽然硬件上回绕为 -128,但 C 标准视为 UB,不应依赖此行为。应改用无符号或检测溢出。

#
★★

9. 为何 C 语言中 INT_MAX + 1 是未定义行为,而 UINT_MAX + 1 在无符号下有定义?

请解释为什么 C 语言中 INT_MAX + 1 是未定义行为,而 UINT_MAX + 1 在无符号下有定义?

  • 有符号溢出 UB
  • 无符号回绕定义
  • 无符号按模 2^n 定义回绕

C 标准规定有符号整数溢出是未定义行为(UB),因为历史上有非二进制补码的实现,且 UB 允许编译器自由优化。而无符号整数运算按模 2^n 定义,UINT_MAX + 1 定义为回绕到 0,行为确定。因此有符号溢出不能依赖,无符号回绕是标准保证的。

无符号回绕有数学定义(模运算),有符号则留给实现。写可靠代码时,有符号运算应避免溢出(用宽类型、检测或 __builtin_add_overflow),无符号可安全利用回绕。

#
★★

10. 把 0.1 累加 100 次与单次乘以 100 比较结果,说明为何有差异?

请比较把 0.1 累加 100 次与单次乘以 100 的结果,说明为何存在差异?

  • 浮点误差累积
  • 累加 vs 乘法
  • 多次舍入误差累积放大

0.1 无法精确表示,每次累加 0.1 都会引入舍入误差,累加 100 次误差会累积。单次 0.1×100 只进行一次舍入,误差较小。因此累加 100 次的结果可能不等于 10.0,而单次乘法更接近 10.0。典型结果:累加 100 次得到 9.99999999999999 或 10.000000000000002,乘法得到更接近 10 的值。

误差来源是 0.1 的表示误差。累加 100 次误差累积放大,乘法只舍入一次。减少误差可用 Kahan 求和或改用十进制类型。

#
★★

11. 整数溢出的检测中,无符号回绕与有符号溢出的 UB 有何区别?

请说明整数溢出的检测方法,以及无符号回绕与有符号溢出在 C 中的区别?

  • 溢出检测方法
  • 无符号回绕与有符号 UB
  • 无符号可用 result < a 检测进位

无符号溢出按模回绕,行为确定,检测可用 result < a(对加法 a+b 且 b>0 时)判断进位。有符号溢出是 UB,不应直接依赖,检测可用 __builtin_add_overflow(GCC/Clang)或 __builtin_add_overflow 系列,或先比较 b 与 INT_MAX - a 再运算。C23 提供了 _STDC*_OVERFLOW 检测。通用策略:用宽类型、比较式检测或内建溢出函数。

无符号回绕有定义,可用大小比较检测;有符号溢出 UB,必须用非溢出方式(内建函数或预先比较)检测。这是安全代码的关键。

#
★★

12. 浮点舍入中,IEEE 754 的四种舍入模式与银行家舍入如何工作?

请说明浮点舍入,包括 IEEE 754 的四种舍入模式与银行家舍入的关系?

  • 四种舍入模式
  • 银行家舍入
  • 平分时取偶数尾数消除偏差

IEEE 754 四种模式:向最近偶数(默认)、向零、向下、向上。银行家舍入是向最近偶数在十进制下的称呼:当数值位于两个候选数正中间时,取偶数尾数,而不是总进位。例如 0.5 舍入为 0,1.5 舍入为 2,2.5 舍入为 2。它避免了传统四舍五入在大量数据求和时产生的系统性正偏差。

银行家舍入本质是向最近偶数,用于金融计算减少统计偏差。JS 的 Math.round 与 C 的 printf 默认用四舍五入不对等,需注意。

#
★★

13. FMA(fused multiply-add)指令如何避免 a×b+c 的中间结果双重舍入(double rounding),对数值稳定性有何意义?

请解释 FMA(fused multiply-add)指令如何避免 a×b+c 的中间结果双重舍入,及其对数值稳定性的意义?

  • FMA 语义
  • 双重舍入问题
  • 中间乘积不单独舍入的单次舍入

普通 ab+c 先计算 ab 并舍入到最终精度,再加 c 再舍入,可能发生两次舍入(double rounding),累积误差。FMA 指令在一次运算中计算 a*b+c,中间乘积不单独舍入,只对最终结果做一次舍入,避免了中间舍入误差。这对多项式和求积运算的数值稳定性很重要,能减少误差并提高精度。

FMA 用扩展精度保留中间乘积,只舍入一次。常用于求和、点积、多项式求值(如 Horner 法),也是 Kahan 类算法的基础。GCC 可用 -ffp-contract=fast 或 fma() 函数利用。

#
★★

14. Kahan 补偿求和如何把大量小数累加的误差从随 n 增长降到近常数?以 0.1 累加 100 万次说明误差改善?

请解释 Kahan 补偿求和如何把大量小数累加的误差从随 n 增长降到近常数,并以 0.1 累加 100 万次为例说明误差改善?

  • Kahan 求和算法
  • 误差补偿原理
  • 补偿变量保存被舍去的低阶误差

朴素累加每次舍入误差累积,误差随 n 增长(O(n))。Kahan 求和用补偿变量 c 记录每一位被舍去的低阶误差,并在下一次加法中加回,使误差只与最终舍入有关,降到近乎常数(O(1))。以 0.1 累加 100 万次:朴素方法结果偏离 100000 较多,Kahan 方法结果接近 100000,误差改善显著。算法:对每个 x,y = x - c;t = sum + y;c = (t - sum) - y;sum = t。

c 保存在上次加法中丢失的误差,让误差被补偿而非累积。它是数值稳定求和的经典方法,复杂度不变,只是多了减法,精度大幅提升。

#

15. Java/Kotlin int 与 long 溢出在业务安全场景的检测与防护

请说明 Java/Kotlin 中 int 与 long 溢出在业务安全场景中的检测与防护?

  • Java 溢出行为
  • 溢出防护策略
  • Math.addExact 溢出时抛异常

Java/Kotlin 整数溢出是静默回绕,不报错,可能造成安全漏洞(如金额计算、ID 生成、长度计算)。防护方法:使用 Math.addExact/subtractExact/multiplyExact 等,溢出时抛 ArithmeticException;用 BigInteger 或 long 的更大范围;校验运算结果与操作数符号;使用 AtomicLong 或 LongAccumulator 等。业务中金额建议用 BigDecimal 或整数分,避免浮点。

Java 溢出静默,需显式检测。Exact 系列在溢出时抛异常,配合 try-catch 或预先校验,是安全防护的主要手段。

#

16. 解释有符号整数溢出与无符号整数溢出在 C 语言中的不同语义?

请解释有符号整数溢出与无符号整数溢出在 C 语言中的不同语义?

  • 有符号 UB
  • 无符号回绕
  • 无符号回绕可用于环形缓冲

C 中无符号整数运算按模 2^n 定义,溢出结果明确(回绕),行为有定义。有符号整数溢出是未定义行为(UB),编译器可假定不会发生并做优化,程序行为不可预测。因此无符号回绕可安全利用(如环形缓冲),有符号则必须避免或检测。

语义差异是 C 的重要特性。无符号回绕确定,有符号 UB。写代码时明确类型,利用无符号回绕或用内建溢出检测处理有符号。

#

17. 浮点比较的陷阱,为什么不能用 ==,以及 EPSILON 与 ULP 比较的用法?

请解释浮点比较的陷阱:为什么不能用 ==,以及 EPSILON 与 ULP 比较方法?

  • 浮点 == 陷阱
  • EPSILON 与 ULP
  • 绝对误差对数量级敏感而相对误差更稳健

浮点运算有舍入误差,直接用 == 比较可能因微小误差而失败,例如 0.1+0.2 != 0.3。应使用绝对误差:|a-b| <= EPSILON,但绝对误差对数量级敏感。更好的方法是 ULP(units in last place)比较,即比较两个数在浮点表示中相差多少个最小可表示间隔,或用相对误差 |a-b| / max(|a|,|b|) <= EPSILON。对同一数量级用绝对 EPSILON,跨数量级用相对误差或 ULP。

== 对浮点不可靠。绝对 EPSILON 对小数、大数不通用,相对误差与 ULP 更稳健。C++ 可用 std::nextafter 或 std::numeric_limits 辅助。

#

18. 整数溢出的检测与防护中,如何判断加法的溢出?

请说明整数溢出的检测与防护,特别是加法的溢出判断方法?

  • 加法溢出检测
  • 防护方法
  • 用边界比较避免先溢出再判断

加法溢出判断:对无符号 a+b,若 result < a 则发生进位(溢出)。对有符号,可判断 b > INT_MAX - a(a>0 时)或 b < INT_MIN - a(a<0 时)。更稳妥用内建函数 __builtin_add_overflow(C/C++)或 Math.addExact(Java)。防护:用宽类型、预检、饱和运算或异常捕获。

无符号用 result < a 判断,有符号用边界比较避免先溢出。内建函数最规范,能同时给出结果与溢出标志。