当前位置:编程学习 > C/C++ >>

安全的整数比较

前几天在水母上看到的题:


正常的比较 assert(-1 < 1U) 是会失败的。因为 -1 会提升成无符号数。
写一个安全的比较函数,使得
template <typename T1, typename T2>
int SafeIntCompare(T1 i1, T2 i2);
如果 i1 真实值 <  i2,返回 -1
     i1 真实值 == i2,返回  0
     i1 真实值 >  i2,返回  1


只有当两个类型一个是有符号、另一个是无符号时,才需要特殊处理。
对类型的符号判断,可以直接判断该类型的-1是否比0小,也可以用标准库std::numeric_limits<T>中的is_signed成员。

简单的做法:
template<typename T1, typename T2>
int SafeIntCompare(T1 v1, T2 v2)
{
  static const bool t1 = std::numeric_limits<T1>::is_signed;
  static const bool t2 = std::numeric_limits<T2>::is_signed;
  if (t1 != t2) {
    if (t1 && v1 < 0) return -1;
    if (t2 && v2 < 0) return 1;
  }
  if (v1 == v2) return 0;
  if (v1 < v2)  return -1;
  return 1;
}

但由于进行比较的两个数可能分别是:有符号数和无符号数,编译时编译器会给出大量的警告。

     要避免有符号数和无符号数的进行直接比较,就必须将它们都转为同一个类型T。这个类型的确定可以采用两种方法:

     1 比较原来两个类型是否是有符号数以及它们所占用的字节数,来推断出应该将它们都转为哪种类型T,这是vc那个safeint的做法。

     2 采用这个trick:将这两个类型的数(数可以取0)直接相加,得到的结果的类型就是所求的。这是因为:两个数进行比较时,采用的类型转换规则和两个数相加时所采用的规则是一致的。


改成后的代码

 

template<bool> struct Assert {};
template<> struct Assert<false>;

template<bool is_first_negtive, bool is_second_negtive>
struct SafeIntCmpImpl
{
  template<typename T1, typename T2>
  static int int_cmp(T1 v1, T2 v2)
  {
    if (v1 == v2) return 0;
    if (v1  < v2) return -1;
    return 1;
  }
};

template<>
struct SafeIntCmpImpl<true, false>
{
  template<typename T1, typename T2, typename T3>
  static int int_cmp(T1 v1, T2 v2, T3)
  {
    return SafeIntCmpImpl<true, true>::int_cmp(T3(v1), T3(v2));
  }

  template<typename T1, typename T2>
  static int int_cmp(T1 v1, T2 v2)
  {
    return v1 < 0 ? -1 : int_cmp(v1, v2, T1(0) + T2(0));
  }
};

template<>
struct SafeIntCmpImpl<false, true>
{
  template<typename T1, typename T2>
  static int int_cmp(T1 v1, T2 v2)
  {
    return -SafeIntCmpImpl<true, false>::int_cmp(v2, v1);
  }
};

 

template<typename T1, typename T2>
int SafeIntCompare(T1 v1, T2 v2)
{
  typedef std::numeric_limits<T1> M1;
  typedef std::numeric_limits<T2> M2;
  static const bool is_arg_valid = M1::is_integer & M2::is_integer;
  Assert<is_arg_valid>();
  return SafeIntCmpImpl<M1::is_signed, M2::is_signed>::int_cmp(v1, v2);
}

  但上面的写法有一个问题:如果一个 short和一个unsigned char进行比较,编译器都是转为int进行比较,没有必要进行特殊处理(上面的代码处理后会多一个与0的比较)。实际上,如果两个类型都是转为有符号类型,可以直接进行比较。
最终代码:

template<typename T>
struct IsSigned {
  static const bool value = T(-1) < T(0);
};

template<bool> struct Assert {};
template<> struct Assert<false>;

template<int> struct Type {};
typedef Type<0> TagNormal;
typedef Type<1> TagFirstArgIsSigned;
typedef Type<2> TagSecondArgIsSigned;

template<typename T1, typename T2, typename T3>
int SafeIntCompare(T1 v1, T2 v2, T3, TagNormal)
{
  if (v1  < v2) return -1;
  if (v1 == v2) return 0;
  return 1;
}

template<typename T1, typename T2, typename T3>
int SafeIntCompare(T1 v1, T2 v2, T3 v3, TagFirstArgIsSigned)
{
  if (v1 < 0) return -1;
  return SafeIntCompare(T3(v1), T3(v2), v3, TagNormal());
}

template<typename T1, typename T2, typename T3>
int SafeIntCompare(T1 v1, T2 v2, T3 v3, TagSecondArgIsSigned)
{
  if (v2 < 0) return 1;
  return SafeIntCompare(T3(v1), T3(v2), v3, TagNormal());
}

template<typename T1, typename T2, typename T3>
int SafeIntCompare(T1 v1, T2 v2, T3 v3)
{
  typedef std::numeric_limits<T1> M1;
  typedef std::numeric_limits<T2> M2;
  typedef std::numeric_limits<T3> M3;
  static const bool is_arg_valid = M1::is_integer & M2::is_integer;
  Assert<is_arg_valid>();
  static const int type_idx = M3::is_signed ? 0 : (M1::is_signed + M2::is_signed * 2) % 3;
  return SafeIntCompare(v1, v2, v3, Type<type_idx>());
}


template<typename T1, typename T2>
int SafeIntCompare(T1 v1, T2 v2)
{
  return SafeIntCompare(v1, v2, T1(0) + T2(0));
}
 

摘自  雁过无痕
 

 

补充:软件开发 , C++ ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,