将uint8_t转换为sint8_t

时间:2010-10-08 17:35:35

标签: 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);
)

有更好/更简单的方法吗?也许是一种不使用条件的方式?

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

  • sint8_t名为int8_t
  • 1281 << 7表示,而不是1 << 8
  • 表示
  • 2s补充是“一个人否定”

:)

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

#include <stdint.h>

int8_t DESER_INT8(uint8_t x)
(
  return ((x >= (1 << 7))
          ? -(UINT8_MAX - x + 1)
          : x);
)

7 个答案:

答案 0 :(得分:12)

1u << 8u0x100u,大于每个uint8_t值,因此永远不会满足条件。你的“转换”例程实际上只是:

return x;

实际上是有道理的。

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

  

当整数类型的值为   转换为另一种整数类型   除了_Bool之外,如果值可以   由新类型代表,它是   不变。

     

...

     

否则,新类型已签名并且   价值无法在其中表现;   结果是   实现定义或   实现定义的信号是   提高。

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

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

return x < 128 ? x : x - 256;

x - 256int,保证将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;
}
使用-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

请注意,优化后所有三个实现都是相同的。 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

在“通常”案件没有成本的情况下保护您的实施免受极端情况从未“愚蠢”。

对于这增加了不必要的复杂性的论点,我说:哪个更难 - 写一个注释来解释转换及其原因,或者你的继任者实习生试图在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;
}

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

答案 1 :(得分:5)

uint8_t转换为int8_t基本上会颠倒两个半范围的顺序。 “高”数字变得“低”。这可以通过XOR完成。

x ^ 0x80

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

return ( x ^ 0x80 ) - 0x80;

你去吧!

答案 2 :(得分:2)

我不知道这是否有任何实际价值,但这是一种不同的方法,我想到了:

uint8_t input;
int8_t output;
*(uint8_t *)&output = input;

请注意:

  • int8_t必须是两个补充。
  • 对应的有符号和无符号类型需要对其范围的重叠部分具有相同的表示,以便可以通过任一类型的指针访问有符号和无符号类型范围内的值。
  • 只留下一位,必须是二进制补码位。

我可以看到这个推理可能无效的唯一方法是CHAR_BIT>8和8位整数类型是带有陷阱位的扩展整数类型,以某种方式标记是否值已签名或未签名。但是,明确使用char类型的以下类似代码永远不会失败:

unsigned char input;
signed char output;
*(unsigned char *)output = input;

因为char类型不能有填充/陷阱位。

潜在的变体是:

return ((union { uint8_t u; int8_t s; }){ input }).s;

char类型:

return ((union { unsigned char u; signed char s; }){ input }).s;

编辑正如Steve Jessop在另一个答案中指出的那样,int8_tuint8_t如果存在填充位,则不需要填充位,因此它们的存在意味着{{1 }}。所以我相信这种方法是有效的。话虽如此,我仍然永远不会使用CHAR_BIT==8并始终显式使用uint8_t,以防实现将unsigned char实现为等大小的扩展整数类型,因为uint8_t类型具有关于别名规则和类型惩罚的特殊权限,这使得它们更受欢迎。

答案 3 :(得分:0)

假设类型sint8_tuint8_t是分配兼容的,这可以正常工作

sint8_t DESER_SINT8(uint8_t x) { return x; }

答案 4 :(得分:0)

嗯,...我认为你试图返回x,如果x可以用sint8表示,或者abs(SINT8_MAX - x)如果没有,对吧?

在这种情况下,这是一个有效的(我认为你的错误很小):

#define HIGHBIT(X) ((X) & (1 << (sizeof(X) * 8 - 1)))

char utos8(unsigned char ux)
{
    return HIGHBIT(ux) ? -ux : ux;
}

请注意,使用该代码,您可以从任意无符号转换为带符号类型将HIGHTBIT宏包装在函数中。

希望有所帮助。

答案 5 :(得分:0)

如果你想避开分支,你总是可以做这样疯狂的事情:

int selector= 127 - x; // 0 or positive if x <=127, negative otherwise
int selector>>= 8; // arithmetic rotate to get -1 or 0
int wrapped_value= x - 256;

return (x&~selector)|(wrapped_value&selector); // if selector is 0, use x, otherwise, use the wrapped value.

答案 6 :(得分:-1)

假设sint8_t int8_t真的来自<stdint.h>,那么它保证了两个补码形式,并且保证没有填充位。

进一步假设您希望相反(隐式)转换起作用并产生原始值。

然后,给定v类型的值uint8_t,您所要做的就是......

    int8_t( v )

就是这样。

C标准确实AFAIK不保证这种转换,只是相反的转换。但是,没有已知的系统或编译器无法工作(假设您有这些类型可用)。

忘记所有手动比特摆弄。或者,要测试您是否正确,只需将值转换回uint8_t并检查是否获得所有情况的原始值。特别是,您使用的公式得出 - ((2 ^ n-1)-x)= 1 + x-2 ^ n,而值保存的正确转换是x-2 ^ n。

干杯&amp;第h。,

- Alf