【问题标题】:Why does std::abs return signed types为什么 std::abs 返回有符号类型
【发布时间】:2013-04-12 15:17:04
【问题描述】:

当我将std::abs(int) 与unsigned 进行比较时,我收到了有关有符号与无符号比较的警告。事实上,std::abs 返回有符号值。为什么会做出这样的选择?它本来可以解决负值的绝对值不能用有符号类型表示的问题。

然后,有没有比这更干净(即没有演员表)的东西来避免警告?

#include <cassert>
#include <cstdlib>

// max(1, lhs + rhs). (lhs must be > 0)
unsigned add(unsigned lhs, int rhs)
{
  return
    (0 < rhs || static_cast<unsigned>(-rhs) < lhs
     ? rhs + lhs
     : 1);
}

int main()
{
  assert(add(42, -41) == 1);
  assert(add(42, 0) == 43);
  assert(add(42, 1) == 43);
  assert(add(42, -51) == 1);
}

【问题讨论】:

  • 我怀疑这对标准原理有多大影响,但实际上并不能保证unsigned 可以代表int 的每个绝对值。理论上,您可以有一个一致的实现,其中int 是 33 位 2 的补码,unsigned 是 32 位。在该实现中,int 和 unsigned 都不能代表 INT_MIN 的绝对值。当然,不确定您为什么想要这样的实现,但它是合法的,因此您的提议并没有严格解决问题。该标准仍然允许您无法执行 abs(INT_MIN)!
  • 在实践中,有一个 int 的值是你不能做 abs 的事实并不比有一个 int 的值更麻烦你不能在上面做--。同样是一个你不能做的值++。 C 和 C++ 的作者并不讨厌未定义的溢出行为以将其删除。
  • @SteveJessop 给定 §3.9.1-3 怎么可能(unsigned int 和 int 的存储量和对象表示相同)?
  • @ChristianRau:不同数量的填充位。位是否参与值不是对象表示的一部分,它是值表​​示的一部分。这很学术,但 C 和 C++ 决心允许有趣的架构。我说好笑:快要好笑了。 32 和 31 比 33 和 32 更合理,但是,8 位字节。我不确定,但我认为可能有一些机器在无符号类型中未使用符号位(尽管unsigned char 必须使用它)。他们是否有符合标准的 C++ 编译器是另一回事。
  • 克雷没有这样的int 类型吗? IIRC 它使用float 布局,但指数位设置为零。对于unsigned int,可能符号位也设置为零。

标签: c++ unsigned signed gcc-warning


【解决方案1】:

简短的回答是这样做是为了使abs 的返回类型与其输入类型相同。大多数时候,这正是您想要的。

大多数情况下,在调用 abs 时,您正在处理一个所有元素都属于同一类型的方程式(或者您会收到警告),并且您希望在该方程式中使用某个变量的大小。这并不意味着您要更改方程式中某个变量的类型。这会给出你提到的那种问题/警告。

因此,简而言之,在要求有符号变量的绝对值时,想要相同的输入和输出类型更为常见和自然。值的大小不常用作指标。

【讨论】:

  • +1 绝对。最后,它是一个数学函数,不应该把我的类型弄乱。您也不希望 std::abs(1.0) 返回 unsigned double。同样,std::floor 不会返回 int(好吧,除了单纯的概念类型守恒之外,这可能还关注可表示范围)。
  • @ChristianRau:我不知道unsigned double 是什么,但我当然不想要一个永远 ;-) 有些人会这么说关于无符号整数类型。
  • @ChristianRau 正是我的意思!另外,我同意@SteveJessop 的观点,unsigned double 听起来很没有动力,我当然不会在标准中错过它。
  • @SteveJessop & Argentlien 好吧,在数字不能为负数的情况下,您永远不知道何时需要额外的精度。但是好吧,我猜这可能会使实际的计算机器变得不必要地复杂(因为不受 IEEE 标准化和硬件实现支持的浮点类型无论如何都是无用的)。但无论如何我都在徘徊;)
  • @SteveJessop:“有些人对无符号整数类型也会这么说”。好吧,我确实认识一些在 Java 中工作的人,并且每天都因为它没有无符号整数而对它大发雷霆。
【解决方案2】:

那不是选择,那是定义。 abs 是通过模板实现的,该模板返回存储原始值的类型。结果将始终有效,因为有符号整数的绝对值将始终适合其原始数据类型。对目标数据类型的显式转换应该足以消除任何警告...

【讨论】:

  • “一个有符号整数的绝对值总是适合他的原始数据类型”——这不是真的。在几乎所有的实现中,INT_MIN 的绝对值都不适合int,abs(INT_MIN) 会导致溢出。
  • abs is implemented via a template。我知道的abs 不是模板。
  • 这些是根据标准存在的 abs 的变体:en.cppreference.com/w/cpp/numeric/math/abs 其中唯一被模板化的是那些将模板类型作为输入的变体。 (valarray 和复数)。
  • 我不明白“那是别无选择,那是定义......通过模板实现”。您似乎声称该函数的接口是由它的实现决定的(它基于已经报告的重载)。当然它必须是相反的。
  • 它是一个复杂值的模板:template T abs (const complex& x);但我将其与标准实现混淆了。你们俩都是对的。
【解决方案3】:

在 c++11 中,您可以编写自己的代码来自动转换:

#include <utility>

template< typename T >
typename std::make_unsigned<T>::type  abs( T x )
{
    //We need to cast before negating x to avoid the overflow.
    return x < 0? -static_cast<std::make_unsigned<T>::type>(x) : x;
}

我尝试使用 -Wall 选项,没有收到任何警告。

【讨论】:

  • 顺便说一句,这段代码在通过INT_MIN时仍然有UB,它没有“解决不能表示绝对值的负值问题”。但它确实节省了每次使用它时都要输入static_cast。
  • 我也是这么想的,但后来我尝试了: int x = std::numeric_limits::min();无符号 y = my_abs(x); cout
  • 问题在于-x 的类型为T,在您有机会转换为无符号类型之前,它会溢出2 的补码最小值。在您的实现中,当您对其进行测试时,溢出是无害的,但不能保证它是无害的。一个安全的替代方案是return (x &lt; 0) ? static_cast&lt;std::make_unsigned&lt;T&gt;&gt;(-(x+1))+1 : x;,你可以希望优化器会解决它。
  • 我明白了, -static_cast<:make_unsigned>>(x) 就足够了,不是吗?而不是强迫它溢出。 ?
  • 是的,我认为这行得通。您甚至可以使用 -static_cast&lt;decltype(my_abs(x))&gt;(x) 将其缩短一点,而不是重复返回类型定义 :-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-11
  • 1970-01-01
  • 1970-01-01
  • 2012-06-21
  • 2014-02-18
相关资源
最近更新 更多