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

时间:2019-02-23 08:05:37

标签: c++ types typetraits signed negative-zero

是否可以编写类型特征来确定类型是否在C ++中支持负零(包括诸如sign-and-magnitude之类的整数表示形式)?我看不到有任何直接做到这一点的东西,而且std::signbit似乎不是constexpr

为了澄清:我问是因为我想知道这是否可能,不管用例可能是什么(如果有)。

4 个答案:

答案 0 :(得分:4)

不幸的是,我无法想象实现这一目标的方法。事实是,C标准认为类型表示形式不应该成为程序员的关注点(*),而只是用来告诉执行器应该做什么。

作为一名程序员,您所要做的就是:

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

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

答案 1 :(得分:3)

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

  • reinterpret_cast<char*>(&value)constexpr中被禁止。
  • 也禁止使用union类型来绕过constexpr中的上述规则。
  • 整数类型的零和负零上的运算的行为与每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,则不可能有符号零。这种保证非常薄弱,但是我看不到任何更强有力的保证。同样,它也没有对浮点类型有建设性的意见。我找不到任何合理的方法来测试浮点类型。

答案 2 :(得分:2)

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

无论如何,十进制机器都不再被允许,并且随着年龄的增长,只有一个负零。实际上,这些测试就足够了:

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

,但是您的代码无法正常工作有两个原因。尽管该标准说了什么,但constexprs仍使用主机规则在主机上进行评估,并且存在一种体系结构,该体系结构在编译时会崩溃。

无法按摩陷阱。 ~(unsigned)0 == (unsigned)-1可靠地测试了2的恭维,因此它的反函数确实检查了自己的恭维*;但是,~0是在恭维中产生负零的唯一方法,并且将该值用作有符号数的任何方式都可以捕获,因此我们无法测试其行为。即使使用平台特定的代码,我们也无法捕获constexpr中的陷阱,因此请忘了。

*除非真正使用奇异运算,否则嘿

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

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

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
}

答案 3 :(得分:1)

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

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

因此您可以使用static_assert(signbit(-0))进行检查。但是,有一个脚注(强调我的意思)

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

不幸的是,这意味着您仍然必须依靠带有负零的浮点类型。您可以使用std::numeric_limits<double>::is_iec559

强制使用带零号的IEEE-754

类似地,std::copysign具有可用于此目的的重载Promoted copysign ( Arithmetic1 x, Arithmetic2 y );。不幸的是,尽管有一些建议,signbitcopysign都不是当前标准的constexpr

如果您不想等待标准更新,请Clang and GCC can already consider those constexprHere's their results


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

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也是libraries defined INT_MIN in two's complement

的方式

但是最简单的解决方案是消除非二进制补码案例,如今这种情况几乎不存在

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

相关: