安全的整数比较

安全的整数比较

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

正常的比较 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<truefalse>
{
  template
<typename T1, typename T2, typename T3>
  
static int int_cmp(T1 v1, T2 v2, T3)
  {
    
return SafeIntCmpImpl<truetrue>::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<falsetrue>
{
  template
<typename T1, typename T2>
  
static int int_cmp(T1 v1, T2 v2)
  {
    
return -SafeIntCmpImpl<truefalse>::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 ));
}

你可能感兴趣的:(安全的整数比较)