将uint8_t转换为sint8_t

hec*_*rot 4 c c++ serialization

在便携式C中将"uint8_t"转换为"sint8_t"的最佳方法是什么?

那是我提出的代码......

#include <stdint.h>

sint8_t DESER_SINT8(uint8_t x)
(
  return
     (sint8_t)((x >= (1u << 8u))
               ? -(UINT8_MAX - x)
               : x);
)
Run Code Online (Sandbox Code Playgroud)

有更好/更简单的方法吗?也许没有使用条件的方式?

编辑:谢谢你们.总而言之,我已经学到了什么......

  • sint8_t 真的叫 int8_t
  • 128是表达1 << 7而不是表达1 << 8
  • 2s补充是"一个人否定"

:)

所以这是我原始代码的更新版本:

#include <stdint.h>

int8_t DESER_INT8(uint8_t x)
(
  return ((x >= (1 << 7))
          ? -(UINT8_MAX - x + 1)
          : x);
)
Run Code Online (Sandbox Code Playgroud)

Ste*_*non 12

1u << 8u0x100u,它大于每个uint8_t值,因此从不满足条件.您的"转换"例程实际上只是:

return x;
Run Code Online (Sandbox Code Playgroud)

这实际上是有道理的.

您需要更清楚地定义转换所需的内容.C99定义了从无符号到有符号整数类型的转换,如下所示(§6.3.1.3"有符号和无符号整数")

当具有整数类型的值转换为除了之外的另一个整数类型时_Bool,如果该值可以由新类型表示,则它将保持不变.

...

否则,新类型将被签名,并且值无法在其中表示; 结果是实现定义的,或者引发实现定义的信号.

因此,uint8_t之间的值0127被保留,而对于大于值的行为127是未定义的. 许多(但不是全部)实现将简单地将无符号值解释为有符号整数的二进制补码表示.也许您真正要问的是如何保证跨平台的这种行为?

如果是这样,您可以使用:

return x < 128 ? x : x - 256;
Run Code Online (Sandbox Code Playgroud)

值为x - 256a int,保证将值x解释为二进制补码8位整数.隐式转换int8_t然后保留此值.

这一切都假设sint8_tint8_t,而sint8_t不是标准类型.如果不是,那么所有的赌注都是关闭的,因为我建议的转换的正确性取决于int8_t具有二进制补码表示的保证(§7.18.1.1"精确宽度整数类型").

如果sint8_t是一些古怪的平台特定类型,它可能使用一些其他表示,例如一个补码,它具有一组不同的可表示值,从而使某些输入的上述转换实现定义(因此是非可移植的).


编辑

Alf认为这是"愚蠢的",并且在任何生产系统中都不需要这样做.我不同意,但它确实是一个角落案件的角落案件.他的论点并非完全没有价值.

然而,他声称这种"低效"并因此应该避免是毫无根据的.合理的优化编译器将在不需要的平台上优化它.例如在x86_64上使用GCC:

#include <stdint.h>

int8_t alf(uint8_t x) {
    return x;
}

int8_t steve(uint8_t x) {
    return x < 128 ? x : x - 256;
}

int8_t david(uint8_t x) {
    return (x ^ 0x80) - 0x80;
}
Run Code Online (Sandbox Code Playgroud)

使用-Os -fomit-frame-pointer编译产生以下内容:

_alf:
0000000000000000    movsbl  %dil,%eax
0000000000000004    ret
_steve:
0000000000000005    movsbl  %dil,%eax
0000000000000009    ret
_david:
000000000000000a    movsbl  %dil,%eax
000000000000000e    ret
Run Code Online (Sandbox Code Playgroud)

请注意,优化后所有三种实现都是相同的.Clang/LLVM给出了完全相同的结果.同样,如果我们为ARM而不是x86构建:

_alf:
00000000        b240    sxtb    r0, r0
00000002        4770    bx  lr
_steve:
00000004        b240    sxtb    r0, r0
00000006        4770    bx  lr
_david:
00000008        b240    sxtb    r0, r0
0000000a        4770    bx  lr
Run Code Online (Sandbox Code Playgroud)

当"通常"案件没有成本时,保护您的实施免受极端情况从来都不是"愚蠢".

对于这增加了不必要的复杂性的论点,我说:哪个更难 - 写一个注释来解释转换以及它为什么存在,或者你的继任者的实习生试图调试问题10年后新编译器打破了幸运你一直默默地依赖这一切的偶然事件?以下真的很难维护吗?

// The C99 standard does not guarantee the behavior of conversion
// from uint8_t to int8_t when the value to be converted is larger
// than 127.  This function implements a conversion that is
// guaranteed to wrap as though the unsigned value were simply
// reinterpreted as a twos-complement value.  With most compilers
// on most systems, it will be optimized away entirely.
int8_t safeConvert(uint8_t x) {
    return x < 128 ? x : x - 256;
}
Run Code Online (Sandbox Code Playgroud)

当一切都说完了,我同意这是模糊的,但我也认为我们应该尝试以面值回答这个问题.当然,更好的解决方案是C标准将有符号类型为二进制补码整数而没有填充(因此指定所有intN_t类型的行为)时,将无符号转换为有符号的行为.

  • @Alf:假设不存在这样的实现是无关紧要的.如果一个平台出现缺乏原生的8位算术,并且钳位恰好比符号扩展更快(完全不是荒谬的,特别是在某些矢量架构上),那么很容易就可以实现这些属性(我不会如果一个人已经存在,我会感到惊讶.更重要的是:当你回答问题时,你无法改变问题.正如所提出的那样,问题是如何以标准的方式*保证*. (2认同)

Pot*_*ter 5

转换uint8_tint8_t基本上颠倒了两个半范围的顺序."高"数字变得"低".这可以通过XOR完成.

x ^ 0x80
Run Code Online (Sandbox Code Playgroud)

但是,所有数字仍然是正数.那不好.我们需要引入正确的符号并恢复正确的幅度.

return ( x ^ 0x80 ) - 0x80;
Run Code Online (Sandbox Code Playgroud)

你去!


pmg*_*pmg 0

假设类型sint8_tuint8_t赋值兼容,这是可行的

sint8_t DESER_SINT8(uint8_t x) { return x; }
Run Code Online (Sandbox Code Playgroud)

  • 这与标准中的规则“[conv.int]”相冲突,该规则表示“如果目标类型是有符号的,则如果可以用目标类型(和位域宽度)表示该值,则该值不会改变;否则,价值是由实现定义的。” (2认同)