程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 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));
}
 

摘自  雁過無痕
 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved