【问题标题】:Is it possible to test whether a type supports negative zero in C++ at compile time?是否可以在编译时测试类型是否支持 C++ 中的负零?
【发布时间】:2019-02-23 08:05:37
【问题描述】:

有没有办法编写类型特征来确定类型是否支持 C++ 中的负零(包括整数表示,例如 sign-and-magnitude)?我没有看到任何直接这样做的东西,而且std::signbit 似乎不是constexpr

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

【问题讨论】:

  • 像往常一样,我更感兴趣的是为什么你需要这个?您需要解决的真正原始问题是什么?除非这只是单纯的好奇(在这种情况下你应该提到它),否则请直接询问你真正的问题。现在你的问题更像是XY problem
  • 您应该知道,非 2s 补码有符号整数选项很可能从 C++ 中消失。有关详细信息,请参阅open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r0.html(其中还引用了 C 中的类似努力)。
  • @paxdiablo - 坦率地说,我希望不会。如果技术以一种使非 2 补码整数类型有用的方式发展,那么 C 和 C++ 标准将明确禁止使用它们(或者规范将不得不重新引入使用它们的能力)。说当前的实现以特定的方式工作是一回事。编译器供应商或程序员应该在他们的开发中假设它是另一回事。出于各种原因,硬件设计确实会给软件开发人员带来惊喜。
  • @Someprogrammerdude - 我很好奇你认为 X 在这种情况下可能是什么让你认为这是一个 XY 问题。
  • @Someprogrammerdude - 我知道什么是 XY 问题,在您发表评论之前我已经遇到过这个术语,无论如何我都能够关注链接和阅读。问题如前所述,我想知道这是否可能。这不是 XY 问题,我不知道你为什么会得出这个结论。

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


【解决方案1】:

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

作为一名程序员,你必须知道的是:

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

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

【讨论】:

  • 因为 C++20 2-补码是唯一的表示
【解决方案2】:

最好的办法是在编译时排除有符号零的可能性,但永远不要完全肯定它在编译时的存在。 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 则不可能有符号零。这个保证很弱,但我看不到任何更强的保证。此外,它对浮点类型没有任何建设性的说明。我找不到任何合理的方法来测试浮点类型。

【讨论】:

  • 而不是检查max() - min() + 1 == 2 ** bits,将表达式更改为min() == 2 ** bits - max() - 1 会更简单,这可以作为min() == -max() - 1 完成,无需任何任意精度数学
  • 这是未定义的行为:2 ** bits 因为它在类型 T 中溢出为零,如果 T 是有符号的。当-max() == min() 时,此- max() -1 对于具有符号位和负零的系统来说是未定义的行为。我没有说任意精度是计算它的最短方法,只是避免溢出(未定义行为)错误的最安全方法。
  • min() == -max() - 1 不调用 UB。结果与以更高的精度评估您的表达式相同
  • @phuclv,在-numeric_limits&lt;int&gt;::max() == numeric_limits&lt;int&gt;::min() 的系统上,写(-max() - 1)(numeric_limits&lt;int&gt;::min() - 1) 相同。下溢int 是未定义行为参见en.cppreference.com/w/cpp/language/… 当有符号整数算术运算溢出(结果不适合结果类型)时,行为未定义
【解决方案3】:

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

无论如何,十进制机器不再被允许使用,而且历代以来只有一个负零。实际上,这些测试就足够了:

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

但您的代码无法正常工作有两个原因。不管标准怎么说,constexprs 是使用主机规则在主机上评估的,并且存在一个架构,它会在编译时崩溃。

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

*除非是真正奇特的算术,但是嘿

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

如果您给我一个实际标准投诉编译器,它在 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
}

【讨论】:

  • 正如我在另一个答案中评论的那样,~0 对于 C 标准是不安全的,我在 C++ 标准上找不到任何措辞或保证。 (1) 在一个补码系统上~0 是负零; (2) 负零可能会根据 C 标准 open-std.org/jtc1/SC22/wg14/www/docs/n1548.pdf (6.2.6.2 Integer types) 陷入陷阱,引用:“as is是否值...带符号位和所有值位 1(用于反码),是陷阱表示或正常值”。
  • @MichaelVeksler:你在这两个方面都是正确的。但是,constexprs 在具有主机算术规则的主机上进行评估的事实更加困难。是的,我知道这是不应该的。我已经追捕了太多次归结为这个的错误。
  • 在这种情况下,我们都同意它在某些编译器中是一个错误区域,并且正确的编译器行为(对于 constexpr)将发出诊断(对于此类陷阱情况)并且不接受代码也不崩溃。我说的对吗?
  • @MichaelVeksler:是的。如果编译器可靠地发出诊断,我可以编写通过使用多个构建步骤实际工作的代码,但事实并非如此。 :(
  • ~0 == 0 在一个补码机器上为真,但在符号幅度一上却不是
【解决方案4】:

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

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

所以您可以与static_assert(signbit(-0)) 联系。但是有一个脚注(强调我的)

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

不幸的是,这意味着您仍然必须依赖负零的浮点类型。您可以通过std::numeric_limits&lt;double&gt;::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");

相关:

【讨论】:

  • @MichaelVeksler 很公平。修复了
  • -1 == ~0 的测试没有说服力,因为:(1)在一个补码系统上~0 是负零; (2)负零可能会根据C标准open-std.org/jtc1/SC22/wg14/www/docs/n1548.pdf(6.2.6.2整数类型)捕获,引用:“as是值...是否带有符号位和所有值位1(用于反码) ), 是陷阱表示或正常值”。 C++ 标准对此并不清楚。
  • @MichaelVeksler 在一个补码系统上,~0 是负零,不等于 -1(其值为 ~1)。在符号大小上,~0-INT_MAX,它也不是 -1。所以它适用于两种情况。但是你是对的,它在陷阱零表示的情况下不起作用
猜你喜欢
  • 2011-09-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-16
相关资源
最近更新 更多