Arduino 中不需要的符号扩展
Unwanted sign extension in Arduino
我正在尝试在 Arduino 中实现逻辑右移(即避免符号扩展),在阅读 Arduino BitShift 指南(https://www.arduino.cc/en/Reference/Bitshift)后,它建议将无符号变量右移,不会导致符号扩展名:
When you shift x right by y bits (x >> y), and the highest bit in x is
a 1, the behavior depends on the exact data type of x. If x is of type
int, the highest bit is the sign bit, determining whether x is
negative or not, as we have discussed above. In that case, the sign
bit is copied into lower bits, for esoteric historical reasons:
int x = -16; // binary: 1111111111110000
int y = x >> 3; // binary: 1111111111111110 This behavior, called sign extension, is often not the behavior you want. Instead, you may
wish zeros to be shifted in from the left. It turns out that the right
shift rules are different for unsigned int expressions, so you can use
a typecast to suppress ones being copied from the left.
在我的测试中,它不是那样工作的:
Serial.print( ((uint32_t)(1<<15)) >> 15, BIN);
打印:
11111111111111111
这意味着,正在进行符号扩展。
我也从那里尝试了建议的示例,结果相同。
我是不是做错了什么?
另外是否可以进行移位并强制操作是逻辑运算而不是算术运算?
首先,我认为您遗漏了一些关键信息:看起来您必须使用 16 位 int
类型的 Arduino 开发板(例如 Arduino Uno)。
这里的问题是整数提升在 C 和 C++ 中的工作方式。当您将 16 位有符号整数文字 1<<15
转换为 32 位无符号整数文字时,将执行以下步骤:
- 您正在从 16 位转换为 32 位,因此它会首先将您现有的文字扩展为 32 位。由于它是有符号文字,因此首先将其符号扩展为 32 位有符号值。
- 现在操作数与所需类型具有相同的位宽,编译器将其转换为 32 位无符号整数类型。
我身边没有 16 位机器来测试它,但我可以使用这个测试程序在我的 64 位笔记本电脑上复制相同的行为:
#include <stdio.h>
#include <inttypes.h>
int main(int argc, const char* argv[]) {
printf("%" PRIx64 "\n", ((uint64_t)(1 << 31)));
// prints ffffffff80000000
printf("%" PRIx64 "\n", ((uint64_t)(1 << 31)) >> 31);
// prints 1ffffffff
return 0;
}
所以你可以在这个程序中看到,不是 >>
操作在进行不需要的符号扩展,而是从 32 位有符号整数到 64 位无符号整数的转换,因为cast 实际上是这样的:
int32_t
→int64_t
→uint64_t
如果你想避免额外的符号扩展,你应该从一个无符号文字开始(正如一些程序员在他的评论中建议的那样),或者转换为相同宽度的无符号类型。这些都应该有效:
Serial.print( ((uint32_t)(1u<<15)) >> 15, BIN);
Serial.print( ((uint16_t)(1<<15)) >> 15, BIN);
Serial.print( ((uint32_t)(uint16_t)(1<<15)) >> 15, BIN);
我正在尝试在 Arduino 中实现逻辑右移(即避免符号扩展),在阅读 Arduino BitShift 指南(https://www.arduino.cc/en/Reference/Bitshift)后,它建议将无符号变量右移,不会导致符号扩展名:
When you shift x right by y bits (x >> y), and the highest bit in x is a 1, the behavior depends on the exact data type of x. If x is of type int, the highest bit is the sign bit, determining whether x is negative or not, as we have discussed above. In that case, the sign bit is copied into lower bits, for esoteric historical reasons:
int x = -16; // binary: 1111111111110000 int y = x >> 3; // binary: 1111111111111110 This behavior, called sign extension, is often not the behavior you want. Instead, you may
wish zeros to be shifted in from the left. It turns out that the right shift rules are different for unsigned int expressions, so you can use a typecast to suppress ones being copied from the left.
在我的测试中,它不是那样工作的:
Serial.print( ((uint32_t)(1<<15)) >> 15, BIN);
打印:
11111111111111111
这意味着,正在进行符号扩展。 我也从那里尝试了建议的示例,结果相同。
我是不是做错了什么? 另外是否可以进行移位并强制操作是逻辑运算而不是算术运算?
首先,我认为您遗漏了一些关键信息:看起来您必须使用 16 位 int
类型的 Arduino 开发板(例如 Arduino Uno)。
这里的问题是整数提升在 C 和 C++ 中的工作方式。当您将 16 位有符号整数文字 1<<15
转换为 32 位无符号整数文字时,将执行以下步骤:
- 您正在从 16 位转换为 32 位,因此它会首先将您现有的文字扩展为 32 位。由于它是有符号文字,因此首先将其符号扩展为 32 位有符号值。
- 现在操作数与所需类型具有相同的位宽,编译器将其转换为 32 位无符号整数类型。
我身边没有 16 位机器来测试它,但我可以使用这个测试程序在我的 64 位笔记本电脑上复制相同的行为:
#include <stdio.h>
#include <inttypes.h>
int main(int argc, const char* argv[]) {
printf("%" PRIx64 "\n", ((uint64_t)(1 << 31)));
// prints ffffffff80000000
printf("%" PRIx64 "\n", ((uint64_t)(1 << 31)) >> 31);
// prints 1ffffffff
return 0;
}
所以你可以在这个程序中看到,不是 >>
操作在进行不需要的符号扩展,而是从 32 位有符号整数到 64 位无符号整数的转换,因为cast 实际上是这样的:
int32_t
→int64_t
→uint64_t
如果你想避免额外的符号扩展,你应该从一个无符号文字开始(正如一些程序员在他的评论中建议的那样),或者转换为相同宽度的无符号类型。这些都应该有效:
Serial.print( ((uint32_t)(1u<<15)) >> 15, BIN);
Serial.print( ((uint16_t)(1<<15)) >> 15, BIN);
Serial.print( ((uint32_t)(uint16_t)(1<<15)) >> 15, BIN);