是否可以在编译时测试类型是否支持负零C++



有没有办法编写类型特征来确定类型是否支持C++中的负零(包括整数表示,如符号和幅度)?我没有看到任何直接做到这一点的东西,std::signbit似乎也没有constexpr.

澄清一下:我问是因为我想知道这是否可能,无论用例是什么,如果有的话。

不幸的是,我想不出一种方法。事实上,C 标准认为类型表示不应该是程序员关心的问题 (*),而只是告诉实现者他们应该做什么。

作为一名程序员,你所需要知道的是:

  • 2-补码不是负整数的唯一可能表示
  • 可能存在负 0
  • 整数的算术运算不能返回负 0,只有按位运算才能

(*) 意见:了解内部表示可能会导致程序员使用旧的良好优化,盲目忽略严格的混叠规则。如果您将某个类型视为只能在标准操作中使用的不透明对象,那么您的可移植性问题就会减少......

最好的办法是在编译时排除有符号零的可能性,但在编译时永远不要完全肯定它的存在。C++标准在防止在编译时检查二进制表示方面大有帮助:

  • reinterpret_cast<char*>(&value)constexpr是被禁止的。
  • constexpr中使用union类型来规避上述规则也是被禁止的。
  • 根据 c++ 标准,对整数类型的零和负零的操作行为完全相同,无法区分。
  • 对于浮点运算,常量表达式中禁止除以零,因此测试1/0.0 != 1/-0.0是不可能的。

唯一可以测试的是整数类型的域是否足够密集以排除有符号零:

template<typename T>
constexpr bool test_possible_signed_zero()
{
using limits = std::numeric_limits<T>;
if constexpr (std::is_fundamental_v<T> &&
limits::is_exact &&
limits::is_integer) {
auto low = limits::min();
auto high = limits::max();
T carry = 1;
// This is one of the simplest ways to check that
// the max() - min() + 1 == 2 ** bits
// without stepping out into undefined behavior.
for (auto bits = limits::digits ; bits > 0 ; --bits) {
auto adder = low % 2 + high %2 + carry;
if (adder % 2 != 0) return true;
carry = adder / 2;
low /= 2;
high /= 2;
}
return false;
} else {
return true;
}
}
template <typename T>
class is_possible_signed_zero:
public std::integral_constant<bool, test_possible_signed_zero<T>()>
{};
template <typename T>
constexpr bool is_possible_signed_zero_v = is_possible_signed_zero<T>::value;

只能保证如果此特征返回 false,则不可能有符号零。这种保证非常微弱,但我看不到任何更有力的保证。此外,它对浮点类型没有任何建设性说明。我找不到任何合理的方法来测试浮点类型。

有人会过来指出这是完全错误的标准。

无论如何,十进制机器不再被允许,古往今来只有一个负零。实际上,这些测试就足够了:

INT_MIN == -INT_MAX && ~0 == 0

但是您的代码不起作用有两个原因。尽管标准说了什么,但 constexprs 是使用主机规则在主机上评估的,并且存在一种在编译时崩溃的架构。

试图按摩陷阱是不可能的。~(unsigned)0 == (unsigned)-1可靠地测试了 2 秒的赞美,所以它确实相反地检查一个人的赞美*;但是,~0是在 1 赞美中生成负零的唯一方法,并且将该值用作有符号数字的任何使用都可能被捕获,因此我们无法测试其行为。即使使用特定于平台的代码,我们也无法在 constexpr 中捕获陷阱,所以忘记它。

*除非真正异国情调的算术,但嘿

每个人都使用#define进行架构选择。如果您需要知道,请使用它。

如果您交给我一个实际的标准投诉编译器,该编译器在 constexpr 中的陷阱上产生编译错误,并使用目标平台规则而不是具有转换结果的主机平台规则进行评估,我们可以这样做:

target.o: target.c++
$(CXX) -c target.c++ || $(CC) -DTRAP_ZERO -c target.c++
bool has_negativezero() {
#ifndef -DTRAP_ZERO
return INT_MIN == -INT_MAX && ~0 == 0;
#else
return 0;
#endif
}

C++中的标准std::signbit函数有一个接收整数值的构造函数

  • bool signbit( IntegralType arg );(4) (自C++11起)

所以你可以检查static_assert(signbit(-0)).但是有一个脚注(强调我的)

  1. 一组重载或接受任何整型 arg 参数的函数模板。等效于 (2)(参数转换为double)。

不幸的是,这意味着您仍然必须依赖负零的浮点类型。您可以强制使用带有带符号零的 IEEE-754

,并带有std::numeric_limits<double>::is_iec559

同样,std::copysign具有可用于此目的的重载Promoted copysign ( Arithmetic1 x, Arithmetic2 y );。不幸的是,signbitcopysign都没有根据当前标准进行constexpr,尽管有一些建议可以做到这一点

  • constexpr for cmath and cstdlib
  • 更多 contexpr 用于 cmath 和复杂
  • Constexpr Math Functions

然而,如果你不想等待标准更新,Clang和GCC已经可以考虑这些constexpr。这是他们的结果


负零的系统也具有平衡范围,因此可以检查正范围和负范围是否具有相同的幅度

if constexpr(-std::numeric_limits<int>::max() != std::numeric_limits<int>::min() + 1) // or
if constexpr(-std::numeric_limits<int>::max() == std::numeric_limits<int>::min())
// has negative zero

事实上,-INT_MAX - 1也是库在二进制补码中定义INT_MIN的方式

但最简单的解决方案是消除非二的补码情况,这些情况现在几乎不存在

static_assert(-1 == ~0, "This requires the use of 2's complement");

相关:

  • 如何检查双精度的位模式是否在 C++11 constexpr 中0x0?

相关内容

  • 没有找到相关文章

最新更新