库函数 | 大事件!std::abs() 失效

题记:10/4/2021 刷 LeetCode 时所遇,被一个奇怪的 case 阻挠,无法 AC

涉及:【原码/补码】【C 库函数 abs】

以下代码执行后结果为?

#include <cstdlib>
#include <iostream>
using namespace std;

int main()
{
    int val = (1 << (sizeof(int)*8 - 1) );	// -2147483648 = -2^31
    val = abs(val);
    cout << val << endl;
}

正确答案:-2147483648 ( abs() 处理后,val 没有发生任何变化)

(⊙ˍ⊙)?WHY?WHY?

迷雾追踪

库函数 std::abs()是绝对值函数,返回传入参数的绝对值!

但很显然,在上述代码中,std::abs()的取绝对值的能力失效了,所以当务之急是找出 std::abs()的实现。

(因为 std::abs()是库函数,而库函数是有 C 标准协会制定函数实现的标准,具体的实现内容,各个平台实现的可能不一致,这里我追寻的是 Linux 平台下的实现版本)

#include <stdlib.h>

#undef	abs

/* Return the absolute value of I.  */
int
abs (int i)
{
  return i < 0 ? -i : i;
}

return i < 0 ? -i : i

??? (⊙x⊙;) ???
-(-2147483648) 不等于 2147483648 吗?难道说,C 语言中的对数取负值的操作并没有我想的那样?

先把自己知道的罗列下:

  1. int 类型的变量大小 4 byte
  2. int 这类的整形变量,采用 “补码” 表示负数
  3. int 能够表示的数据范围:[ -2^31, -1 ] & [ 0, 2^31-1 ]
int val = 1;
val = -val;		// 这个过程是什么样的呢?

在计算机组成的相关书籍中有讲过 “负数用补码表示,补码 = 原码取反 + 1”。

这里我们把上述的理论直接带入看看会得到什么样的结果。

int val =  1;	0000:0001
    val = -val;	1111:1111 [正确结果]
/*
1. 取反 0000:0001 -> 1111:1110
2. 加一 1111:1110 -> 1111:1111 (吻合)
*/

这里我们仅测试了 “正数 -> 负数”,下面我们再测试下 “负数 -> 正数”

int val = -1;	1111:1111
    val = -val; 0000:0001 [正确结果]
/*
1. 减一 1111:1111 -> 1111:1110
2. 取反 1111:1110 -> 0000:0001 (吻合)
*/

现在把 2147483648带入我们刚掌握的计算过程中:

-2147483648 == 10-00-00-00
1. 减一 10-00-00-00 -> 7f-ff-ff-ff
2. 取反 7f-ff-ff-ff -> 10-00-00-00

_(:з)∠)_ AWSL。。。

按照 C 中取负的处理方法,-2147483648 确实会等于自身。

Emmmm,但是为什么会在出现这样的情况呢?貌似这个问题又回到计算机组成原理了。
当时学编码这块时,就学的不是很通透,看来这回要补课啦 >︿<

(以下为分析过程)

int type size = 4 byte = 4 * 8 = 32 bit = 2^32 种可表达数据
数据范围稍微有亿点大,我缩小点:
int type size = 0.5 byte = 4 bit = 16 种可表达数据

1111 [-1]----
1110 [-2]    |
1101 [-3]    |
1100 [-4]    |
1011 [-5]    |
1010 [-6]    |
1001 [-7]    |
1000 [-8]    |
=========    |
0111 [7]     |
0110 [6]     |
0101 [5]     |
0100 [4]     |
0011 [3]     |
0010 [2]     |
0001 [1]     |
0000 [0]-----

负数 -> 正数:-1、取反

正数 -> 负数:取反、+1

其实,这里最应该疑惑的是:

  1. 为什么我要 “取反”
  2. 为什么我要 ”+1“

现在请带着这些疑问看向上面的代码框,仔细分析下里面的规律。

不难发现:“补码表示的负数,跟正数,刚好差一格,就可以轴对称”!!!
这个时候再回过来看 C 语言提供的正负转换机制,可以发现 “+1/-1” 刚好就是为了让正负补齐差距,来达成轴对称的目标。

eg:

1 to -1
1>  1 取反,得 -2 【0001 -> 1110】
2> -2 加一,的 -1 【1110 -> 1111】
库函数 | 大事件!std::abs() 失效

总结:

迷雾追踪总共提炼了两个问题作为道标:

  1. 为什么 val = -2147483648 时,std::abs() 库函数会失效
  2. 为什么 C 语言提供的 “取负” 操作,会让 -2147483648 等于 ta self

问题 ① 解题后的报酬:

  1. “获得了 Linux 提供的 std::abs() 的具体实现”
  2. “C 的取负值操作是如何执行的”

问题 ② 解题后的报酬:

  1. “彻底明白了计算机组成中 补码 & 原码 换算公式是如何推导出来的”
上一篇:C语言---指针笔试题


下一篇:1111