例如,无符号字符如何从-128
到+127
获取值?根据我的理解,最重要的位用于表示数字的符号,而字符的剩余位用于表示数字的大小。现在,7位的最大可能幅度为127
,因此范围不应该从-127
到+127
?如何-128
成为结果?
其次,以下行为背后的位级逻辑是什么
#include <stdio.h>
int main()
{
signed char x = 127;
x += 1;
printf("%i", x);
}
输出:
-128
有人可以看到,x
变为-128
,但为什么呢?这种行为背后的算法是什么?
答案 0 :(得分:5)
这基于名为Two's Complement的东西。这里的想法是,给定一些二进制数,它的两个补码将是它的一个补码(翻转所有位)加一个。我们可以看到一个简单的例子,让我们找到13
的两个补码,我们可以将其写成0b01101
。 01101 (flip) -> 10010 (+1) --> 10011
现在,虽然如果我们像往常一样将其解释为二进制数,我们会以十进制读取19
,但我们必须知道该数字是用二进制补码写的,以便反转过程并得到前一个数字13
。因此,我们可以看到我们已经表示+13 = 01101
和-13 = 10011
这样的事情,请注意正数以0
开头,并且与1
对称。使用此表示时,这将是常量,正数始终以0
开头,而负数以1
开头。值得注意的是,我将0
作为13
原始表示的前缀,这是为了正确表示它的两个补码所需要的。您可以尝试通过相同的示例而不执行此操作并验证它是否必要。
现在,我们来看看这样的几个值,
╔══════╦════════════════╦════════════════════════╗
║ Bits ║ Unsigned Value ║ Two's Complement Value ║
╠══════╬════════════════╬════════════════════════╣
║ 011 ║ 3 ║ 3 ║
╠══════╬════════════════╬════════════════════════╣
║ 010 ║ 2 ║ 2 ║
╠══════╬════════════════╬════════════════════════╣
║ 001 ║ 1 ║ 1 ║
╠══════╬════════════════╬════════════════════════╣
║ 000 ║ 0 ║ 0 ║
╠══════╬════════════════╬════════════════════════╣
║ 111 ║ 7 ║ -1 ║
╠══════╬════════════════╬════════════════════════╣
║ 110 ║ 6 ║ -2 ║
╠══════╬════════════════╬════════════════════════╣
║ 101 ║ 5 ║ -3 ║
╠══════╬════════════════╬════════════════════════╣
║ 100 ║ 4 ║ -4 ║
╚══════╩════════════════╩════════════════════════╝
正如您所看到的,它的工作方式与我们之前的预期相同,但您现在可以开始了解您发现的“错误”。 Two's Complement中4位表示的上限是十进制值3
。让我们看看如何通过向-4
添加1
来达到3 = 0b011
。因此3+1 = 0b100
-4
,您可以从表格中看到,在二人补语中映射到4
(而不是127 = 0b01111111
127 + 1 = 0b10000000
)。你的情况是这个确切的问题,但有更多的位。这样的有符号表示是循环的,因此在顶部溢出会产生最低值。让我们来看看你的案例
1
正如你所看到的那样,它以$vehicles = array();
foreach ($data as $val) {
$temp = array(
'license_plate' => $val->license_plate,
'created_at' => $now,
'updated_at' => $now,
'state_id' => $activeState,
'type_id' => $typeId,
);
array_push($vehicles, $temp);
}
开头,因此它是负数(!),如果你解决了Two's Complement,你会看到它代表-128(因为下限总是大于上限) )。
不是每个硬件都会以同样的方式实现,Intel,AMD,ARM,据我所知,通用CPU的所有主要架构都在其ALU中使用Two补码,但有硬件使用用于实现整数签名的其他技术,因此从根本上说,您描述的行为是未定义的。需要注意的另一个有趣的事情是IEEE's standard for floating point arithmetic,实现基于exponent bias的签名浮点数。
最后,既然我们在这里谈论C,请注意编译器可以优化未定义的行为,this blog post中描述了这种优化的一个很好的例子。
答案 1 :(得分:1)
在C中,运算符+=
的行为是通过=
和+
运算符的等效组合定义的。例如。根据定义,您的x += 1
代表x = x + 1
。由于x
具有窄类型signed char
,因此在任何算术开始之前将其提升为int
。这意味着在x + 1
类型的域中评估子表达式int
。之后,结果(类型int
)将转换回signed char
并存储回x
。
因此,在您的情况下,您的x += 1
实际上等同于
x = (signed char) ((int) x + 1);
(int) x + 1
子表达式不会溢出。它成功生成128
类型的值int
。但是,此值不适合signed char
类型的范围,当此值转换回signed char
类型时,这会导致实现定义的行为。在您的平台上,此实现定义的行为会生成-128
类型的值signed char
。