将有符号的 32 位存储在无符号的 64 位 int 中
Store signed 32-bit in unsigned 64-bit int
基本上,我想要的是 "store" 一个带符号的 32 位整数(在最右边的 32 位中)一个无符号的 64 位整数 - 因为我想将最左边的 32 位用于其他目的.
我现在正在做的是一个简单的演员表和面具:
#define packInt32(X) ((uint64_t)X | INT_MASK)
但这种方法有一个明显的问题:如果 X
是一个正整数(第一位未设置),则一切正常。如果它是负数,它就会变得混乱。
问题是:
如何实现以上,也支持负数,最快最有效的方式?
您需要屏蔽除低位 32 位之外的所有位。您可以使用按位 AND:
#define packInt32(X) (((uint64_t)(X) & 0xFFFFFFFF) | INT_MASK)
32 位负整数将被符号扩展为 64 位。
#include <stdint.h>
uint64_t movsx(int32_t X) { return X; }
x86-64 上的 movsx:
movsx:
movsx rax, edi
ret
屏蔽掉较高的 32 位会导致它只是零扩展:
#include <stdint.h>
uint64_t mov(int32_t X) { return (uint64_t)X & 0xFFFFFFFF; }
//or uint64_t mov(int32_t X) { return (uint64_t)(uint32_t)X; }
x86-64 上的 mov:
mov:
mov eax, edi
ret
https://gcc.godbolt.org/z/fihCmt
这两种方法都不会丢失低 32 位的任何信息,因此这两种方法都是将 32 位整数存储到 64 位整数的有效方法。
普通 mov
的 x86-64 代码短了一个字节(3 字节 vs 4)。我认为应该不会有太大的速度差异,但如果有的话,我希望普通的 mov
能赢一点点。
你提到的 "mess" 发生是因为你将一个小的有符号类型转换为一个大的无符号类型。
在此转换过程中,首先通过应用符号扩展来调整大小。这就是你的麻烦所在。
您可以先将(有符号)整数简单地转换为相同大小的无符号类型。然后转换为 64 位将不会触发符号扩展:
#define packInt32(X) ((uint64_t)(uint32_t)(X) | INT_MASK)
假设对 64 位值的唯一操作是将其转换回 32(并且可能 storing/displaying 它),则无需应用掩码。编译器将在将其转换为 64 位时对 32 位属性进行符号扩展,并在将 64 位值转换回 32 位时选择最低的 32 位。
#define packInt32(X) ((uint64_t)(X))
#define unpackInt32(X) ((int)(X))
或者更好,使用(内联)函数:
inline uint64_t packInt32(int x) { return ((uint64_t) x) ; }
inline int unpackInt32(uint64_t x) { return ((int) x) ; }
一个选项是在回读时解开符号扩展和上限值,但这可能会很混乱。
另一种选择是用位压缩字构造一个并集。然后将问题推迟到编译器进行优化:
union {
int64_t merged;
struct {
int64_t field1:32,
field2:32;
};
};
第三种选择是自己处理符号位。存储 15 位绝对值和 1 位符号。不是超级高效,但如果您遇到无法将负符号值安全地转换为无符号的非 2 的补码处理器,则更有可能是合法的。像母鸡的牙齿一样稀有,所以我自己不会担心这个。
基本上,我想要的是 "store" 一个带符号的 32 位整数(在最右边的 32 位中)一个无符号的 64 位整数 - 因为我想将最左边的 32 位用于其他目的.
我现在正在做的是一个简单的演员表和面具:
#define packInt32(X) ((uint64_t)X | INT_MASK)
但这种方法有一个明显的问题:如果 X
是一个正整数(第一位未设置),则一切正常。如果它是负数,它就会变得混乱。
问题是:
如何实现以上,也支持负数,最快最有效的方式?
您需要屏蔽除低位 32 位之外的所有位。您可以使用按位 AND:
#define packInt32(X) (((uint64_t)(X) & 0xFFFFFFFF) | INT_MASK)
32 位负整数将被符号扩展为 64 位。
#include <stdint.h>
uint64_t movsx(int32_t X) { return X; }
x86-64 上的 movsx:
movsx:
movsx rax, edi
ret
屏蔽掉较高的 32 位会导致它只是零扩展:
#include <stdint.h>
uint64_t mov(int32_t X) { return (uint64_t)X & 0xFFFFFFFF; }
//or uint64_t mov(int32_t X) { return (uint64_t)(uint32_t)X; }
x86-64 上的 mov:
mov:
mov eax, edi
ret
https://gcc.godbolt.org/z/fihCmt
这两种方法都不会丢失低 32 位的任何信息,因此这两种方法都是将 32 位整数存储到 64 位整数的有效方法。
普通 mov
的 x86-64 代码短了一个字节(3 字节 vs 4)。我认为应该不会有太大的速度差异,但如果有的话,我希望普通的 mov
能赢一点点。
你提到的 "mess" 发生是因为你将一个小的有符号类型转换为一个大的无符号类型。 在此转换过程中,首先通过应用符号扩展来调整大小。这就是你的麻烦所在。
您可以先将(有符号)整数简单地转换为相同大小的无符号类型。然后转换为 64 位将不会触发符号扩展:
#define packInt32(X) ((uint64_t)(uint32_t)(X) | INT_MASK)
假设对 64 位值的唯一操作是将其转换回 32(并且可能 storing/displaying 它),则无需应用掩码。编译器将在将其转换为 64 位时对 32 位属性进行符号扩展,并在将 64 位值转换回 32 位时选择最低的 32 位。
#define packInt32(X) ((uint64_t)(X))
#define unpackInt32(X) ((int)(X))
或者更好,使用(内联)函数:
inline uint64_t packInt32(int x) { return ((uint64_t) x) ; }
inline int unpackInt32(uint64_t x) { return ((int) x) ; }
一个选项是在回读时解开符号扩展和上限值,但这可能会很混乱。
另一种选择是用位压缩字构造一个并集。然后将问题推迟到编译器进行优化:
union {
int64_t merged;
struct {
int64_t field1:32,
field2:32;
};
};
第三种选择是自己处理符号位。存储 15 位绝对值和 1 位符号。不是超级高效,但如果您遇到无法将负符号值安全地转换为无符号的非 2 的补码处理器,则更有可能是合法的。像母鸡的牙齿一样稀有,所以我自己不会担心这个。