C++ ';正确的';无符号整数比较
因此,我们都知道C/C++有符号/无符号比较规则,其中C++ ';正确的';无符号整数比较,c++,c,assembly,type-conversion,micro-optimization,C++,C,Assembly,Type Conversion,Micro Optimization,因此,我们都知道C/C++有符号/无符号比较规则,其中-1>2u==true,我想有效地实现“正确”比较 我的问题是,考虑到人们所熟悉的许多体系结构,哪一种更有效。显然,英特尔和ARM的重量更大 鉴于: int x; unsigned int y; if (x < y) {} 这将生成在遇到if;)时通常暗示的预期分支 好吧,您已经正确地描述了这种情况:C/C++无法用单个比较进行完全有符号整数/无符号整数比较 如果升级到int64比进行两次比较快,我会感到惊讶。根据我的经验,编译器非常
-1>2u==true
,我想有效地实现“正确”比较
我的问题是,考虑到人们所熟悉的许多体系结构,哪一种更有效。显然,英特尔和ARM的重量更大
鉴于:
int x;
unsigned int y;
if (x < y) {}
这将生成在遇到
if
;)时通常暗示的预期分支 好吧,您已经正确地描述了这种情况:C/C++无法用单个比较进行完全有符号整数/无符号整数比较
如果升级到int64比进行两次比较快,我会感到惊讶。根据我的经验,编译器非常善于认识到这样的子表达式是纯的(没有副作用),因此不需要第二个分支。(您也可以使用按位或:(x<0)|(x
)明确选择不短路)相比之下,我的经验是编译器往往不会对大于本机字长的整数进行太多特殊情况优化,因此(int64)x<(int64)y
实际上很可能会进行完整的整数比较
总之,没有任何咒语可以保证在任何处理器上生成尽可能最好的机器代码,但对于最常见处理器上最常见的编译器,我猜这两种比较形式不会比升级为int64形式慢
编辑:Godbolt上的一些混乱证实了在ARM32上,GCC在int64方法中使用了太多的机器。VC在x86上也这样做。然而,对于x64,int64方法实际上比指令短一条(因为升级和64位比较非常简单)。不过,流水线可能会使实际性能朝任何方向发展 使用一点模板jiggery扑克,我认为我们可以在所有场景中自动获得最佳结果:
#include<iostream>
#include<cassert>
template<class T> auto make_unsigned(T i) -> T { return i; }
auto make_unsigned(int i) -> unsigned int {
assert(i >= 0);
return static_cast<unsigned int>(i);
}
auto make_unsigned(short i) -> unsigned short {
assert(i >= 0);
return static_cast<unsigned short>(i);
}
auto make_unsigned(long long i) -> unsigned long long {
assert(i >= 0);
return static_cast<unsigned long long>(i);
}
template<
class I1,
class I2,
std::enable_if_t<(std::is_signed<I1>::value and std::is_signed<I2>::value)
or (not std::is_signed<I1>::value and not std::is_signed<I2>::value)>* = nullptr
>
bool unsigned_less(I1 i1, I2 i2) {
return i1 < i2;
};
template<
class I1,
class I2,
std::enable_if_t<std::is_signed<I1>::value and not std::is_signed<I2>::value>* = nullptr
>
bool unsigned_less(I1 i1, I2 i2) {
return (i1 < 0) or make_unsigned(i1) < i2;
};
template<
class I1,
class I2,
std::enable_if_t<not std::is_signed<I1>::value and std::is_signed<I2>::value>* = nullptr
>
bool unsigned_less(I1 i1, I2 i2) {
return not (i2 < 0) and i1 < make_unsigned(i2);
};
int main() {
short a = 1;
unsigned int b = 2;
std::cout << unsigned_less(a, b) << std::endl;
using uint = unsigned int;
using ushort = unsigned short;
std::cout << unsigned_less(ushort(1), int(3)) << std::endl;
std::cout << unsigned_less(int(-1), uint(0)) << std::endl;
std::cout << unsigned_less(int(1), uint(0)) << std::endl;
return 0;
}
#包括
#包括
模板自动生成_无符号(ti)->T{return i;}
自动生成无符号(整数i)->无符号整数{
断言(i>=0);
返回静态_-cast(i);
}
自动使_无符号(短i)->无符号短{
断言(i>=0);
返回静态_-cast(i);
}
自动使_无符号(长i)->无符号长{
断言(i>=0);
返回静态_-cast(i);
}
模板<
第I1类,
第I2类,
std::如果\u t*=nullptr,则启用\u
>
布尔无符号(I1 I1,I2 I2){
返回i1
布尔无符号(I1 I1,I2 I2){
返回(i1<0)或使_无符号(i1)
布尔无符号(I1 I1,I2 I2){
返回not(i2<0)和i1 std::cout您必须根据具体情况进行判断。程序中使用签名类型的原因有几个:
因为在计算或输出中实际上需要负数
“草率的输入”,意思是程序员只是在他们的程序中输入int
,而没有考虑太多
意外签名。编程对象实际上并不想要有符号的数字,但意外地得到了这些数字,要么通过隐式类型升级,要么通过使用整数常量,例如0
,它的类型为int
如果是1),则应使用带符号的算术执行算术。然后应转换为包含最大期望值所需的最小可能类型
例如,假设一个值的范围从-10000
到10000
。然后需要使用一个16位有符号类型来表示它。然后,独立于平台使用的正确类型是int\u fast16\t
int\u fastn\u t
和uint\u fastn\u t
类型要求类型至少与n一样大,但如果它提供更快的代码/更好的对齐,编译器可以选择更大的类型
<P>2)通过研究<代码> STDITE.H./COD>和“停止懒惰”来解决。作为程序员,我们总是需要考虑程序中声明的每个变量的大小和图形。这必须在声明点上进行。或者如果稍后得到某种启示,请返回并更改类型。
如果你不仔细考虑这些类型,你将绝对确定地写出大量的、通常是微妙的错误。这在C++中尤其重要,它比C.</P>更准确地理解类型正确性。
当使用“草率类型”时,实际的预期类型通常是未签名的,而不是签名的。请考虑这个草率的打字示例:
for(int i=0; i<n; i++)
3) 也可以通过学习来解决。特别是这些语言中存在的隐式提升的各种规则,例如通常的算术转换和整数提升。双分支版本肯定会更慢,但实际上,如果您想要布尔整数结果,在x86上,这两者都不是双分支…也不是单分支
例如,X8664 GCC 7.1将用于C++源:
bool compare(int x, unsigned int y) {
return (x < y); // "wrong" (will emit warning)
}
bool compare2(int x, unsigned int y) {
return (x < 0 || static_cast<unsigned int>(x) < y);
}
bool compare3(int x, unsigned int y) {
return static_cast<long long>(x) < static_cast<long long>(y);
}
如果您试图在更复杂的代码中使用这些,它们将在99%的情况下内联(至少零扩展通常会优化掉,因为GCC已经知道它将值写入32位寄存器,隐式地将上半部分归零)
如果不分析,这只是猜测,但“凭直觉”我会将compare3
视为“更快”,尤其是在某些代码中无序执行时(有点可笑,即使对于uint参数,它也会进行适当的32->64升级,而与esi
的上32b中的一些混乱相比,它需要相当多的努力来生成代码调用……但是当它在更复杂的计算中内联时,它可能会将其去掉,因为它
#include<iostream>
#include<cassert>
template<class T> auto make_unsigned(T i) -> T { return i; }
auto make_unsigned(int i) -> unsigned int {
assert(i >= 0);
return static_cast<unsigned int>(i);
}
auto make_unsigned(short i) -> unsigned short {
assert(i >= 0);
return static_cast<unsigned short>(i);
}
auto make_unsigned(long long i) -> unsigned long long {
assert(i >= 0);
return static_cast<unsigned long long>(i);
}
template<
class I1,
class I2,
std::enable_if_t<(std::is_signed<I1>::value and std::is_signed<I2>::value)
or (not std::is_signed<I1>::value and not std::is_signed<I2>::value)>* = nullptr
>
bool unsigned_less(I1 i1, I2 i2) {
return i1 < i2;
};
template<
class I1,
class I2,
std::enable_if_t<std::is_signed<I1>::value and not std::is_signed<I2>::value>* = nullptr
>
bool unsigned_less(I1 i1, I2 i2) {
return (i1 < 0) or make_unsigned(i1) < i2;
};
template<
class I1,
class I2,
std::enable_if_t<not std::is_signed<I1>::value and std::is_signed<I2>::value>* = nullptr
>
bool unsigned_less(I1 i1, I2 i2) {
return not (i2 < 0) and i1 < make_unsigned(i2);
};
int main() {
short a = 1;
unsigned int b = 2;
std::cout << unsigned_less(a, b) << std::endl;
using uint = unsigned int;
using ushort = unsigned short;
std::cout << unsigned_less(ushort(1), int(3)) << std::endl;
std::cout << unsigned_less(int(-1), uint(0)) << std::endl;
std::cout << unsigned_less(int(1), uint(0)) << std::endl;
return 0;
}
for(int i=0; i<n; i++)
for(uint_fast8_t i=0; i<100; i++)
bool compare(int x, unsigned int y) {
return (x < y); // "wrong" (will emit warning)
}
bool compare2(int x, unsigned int y) {
return (x < 0 || static_cast<unsigned int>(x) < y);
}
bool compare3(int x, unsigned int y) {
return static_cast<long long>(x) < static_cast<long long>(y);
}
compare(int, unsigned int):
cmp edi, esi
setb al
ret
compare2(int, unsigned int):
mov edx, edi
shr edx, 31 # sign bit of x
cmp edi, esi
setb al
or eax, edx
ret
compare3(int, unsigned int):
movsx rdi, edi # sign-extend x
mov esi, esi # zero-extend y
cmp rdi, rsi
setl al
ret
int x;
unsigned int y;
if ((x < 0) || (x < y)) {}
#include <cassert>
#include <cstdint>
#include <limits>
using std::intmax_t;
using std::uintmax_t;
template<typename T, typename U>
inline bool safe_gt( T x, U y ) {
constexpr auto tinfo = std::numeric_limits<T>();
constexpr auto uinfo = std::numeric_limits<U>();
constexpr auto maxinfo = std::numeric_limits<intmax_t>();
static_assert(tinfo.is_integer, "");
static_assert(uinfo.is_integer, "");
if ( tinfo.is_signed == uinfo.is_signed )
return x > y;
else if ( maxinfo.max() >= tinfo.max() &&
maxinfo.max() >= uinfo.max() )
return static_cast<intmax_t>(x) > static_cast<intmax_t>(y);
else if (tinfo.is_signed) // x is signed, y unsigned.
return x > 0 && x > y;
else // y is signed, x unsigned.
return y < 0 || x > y;
}
int main()
{
assert(-2 > 1U);
assert(!safe_gt(-2, 1U));
assert(safe_gt(1U, -2));
assert(safe_gt(1UL, -2L));
assert(safe_gt(1ULL, -2LL));
assert(safe_gt(1ULL, -2));
}