diff liboctave/oct-inttypes.cc @ 8185:69c5cce38c29

implement 64-bit arithmetics
author Jaroslav Hajek <highegg@gmail.com>
date Mon, 06 Oct 2008 21:38:49 +0200
parents 66bc6f9b4f72
children 9a0a66f650b1
line wrap: on
line diff
--- a/liboctave/oct-inttypes.cc
+++ b/liboctave/oct-inttypes.cc
@@ -1,6 +1,7 @@
 /*
 
 Copyright (C) 2004, 2005, 2006, 2007 John W. Eaton
+Copyright (C) 2008 Jaroslav Hajek  <highegg@gmail.com>
 
 This file is part of Octave.
 
@@ -28,6 +29,12 @@
 
 #include "oct-inttypes.h"
 
+template<class T>
+const octave_int<T> octave_int<T>::zero (static_cast<T> (0));
+
+template<class T>
+const octave_int<T> octave_int<T>::one (static_cast<T> (1));
+
 // define type names. 
 #define DECLARE_OCTAVE_INT_TYPENAME(TYPE, TYPENAME) \
   template <> \
@@ -43,64 +50,472 @@
 DECLARE_OCTAVE_INT_TYPENAME (uint32_t, "uint32")
 DECLARE_OCTAVE_INT_TYPENAME (uint64_t, "uint64")
 
-static void
-gripe_64bit_mul()
+#ifndef OCTAVE_INT_USE_LONG_DOUBLE
+
+// Define comparison operators
+
+template <class xop> 
+OCTAVE_API bool 
+octave_int_cmp_op::mop (uint64_t x, double y)
+{
+  static const double xxup = std::numeric_limits<uint64_t>::max ();
+  // This converts to the nearest double. Unless there's an equality, the
+  // result is clear.
+  double xx = x;
+  if (xx != y)
+    return xop::op (xx, y);
+  else
+    {
+      // If equality occured we compare as integers.
+      if (xx == xxup)
+        return xop::gtval;
+      else
+        return xop::op (x, static_cast<uint64_t> (xx));
+    }
+}
+
+template <class xop> 
+OCTAVE_API bool 
+octave_int_cmp_op::mop (int64_t x, double y)
 {
-  (*current_liboctave_error_handler) 
-    ("64-bit integer multiplication is not implemented");
+  static const double xxup = std::numeric_limits<int64_t>::max ();
+  static const double xxlo = std::numeric_limits<int64_t>::min ();
+  // This converts to the nearest double. Unless there's an equality, the
+  // result is clear.
+  double xx = x;
+  if (xx != y)
+    return xop::op (xx, y);
+  else
+    {
+      // If equality occured we compare as integers.
+      if (xx == xxup)
+        return xop::gtval;
+      else if (xx == xxlo)
+        return xop::ltval;
+      else
+        return xop::op (x, static_cast<int64_t> (xx));
+    }
+
 }
 
+// We define double-int operations by reverting the operator
+
+// A trait class reverting the operator
+template <class xop>
+class rev_op
+{
+public:
+  typedef xop op;
+};
+
+#define DEFINE_REVERTED_OPERATOR(OP1,OP2) \
+  template <> \
+  class rev_op<octave_int_cmp_op::OP1> \
+  { \
+  public: \
+    typedef octave_int_cmp_op::OP2 op; \
+  }
+
+DEFINE_REVERTED_OPERATOR(lt,gt);
+DEFINE_REVERTED_OPERATOR(gt,lt);
+DEFINE_REVERTED_OPERATOR(le,ge);
+DEFINE_REVERTED_OPERATOR(ge,le);
+
+template <class xop> 
+OCTAVE_API bool 
+octave_int_cmp_op::mop (double x, uint64_t y)
+{
+  typedef typename rev_op<xop>::op rop;
+  return mop<rop> (y, x);
+}
+
+template <class xop> 
+OCTAVE_API bool 
+octave_int_cmp_op::mop (double x, int64_t y)
+{
+  typedef typename rev_op<xop>::op rop;
+  return mop<rop> (y, x);
+}
+
+
+// Define handlers for int64 multiplication
+
 template <>
 uint64_t 
-octave_int_arith_base<uint64_t, false>::mul (uint64_t, uint64_t)
+octave_int_arith_base<uint64_t, false>::mul (uint64_t x, uint64_t y)
 { 
-  gripe_64bit_mul (); 
-  return static_cast<uint64_t> (0);
+  // Get upper words
+  uint64_t ux = x >> 32, uy = y >> 32;
+  uint64_t res;
+  if (ux)
+    {
+      if (uy) 
+        goto overflow;
+      else
+        {
+          uint64_t ly = static_cast<uint32_t> (y), uxly = ux*ly;
+          if (uxly >> 32) 
+            goto overflow;
+          uxly <<= 32; // never overflows
+          uint64_t lx = static_cast<uint32_t> (x), lxly = lx*ly;
+          res = add (uxly, lxly);
+        }
+    }
+  else if (uy)
+    {
+      uint64_t lx = static_cast<uint32_t> (x), uylx = uy*lx;
+      if (uylx >> 32) 
+        goto overflow;
+      uylx <<= 32; // never overflows
+      uint64_t ly = static_cast<uint32_t> (y), lylx = ly*lx;
+      res = add (uylx, lylx);
+    }
+  else
+    {
+      uint64_t lx = static_cast<uint32_t> (x);
+      uint64_t ly = static_cast<uint32_t> (y);
+      res = lx*ly;
+    }
+
+  return res;
+
+overflow:
+  ftrunc = true;
+  return max_val ();
 }
+
 template <>
 int64_t 
-octave_int_arith_base<int64_t, true>::mul (int64_t, int64_t)
+octave_int_arith_base<int64_t, true>::mul (int64_t x, int64_t y)
 { 
-  gripe_64bit_mul (); 
-  return static_cast<int64_t> (0);
+  // The signed case is far worse. The problem is that
+  // even if neither integer fits into signed 32-bit range, the result may
+  // still be OK. Uh oh.
+  
+  // Essentially, what we do is compute sign, multiply absolute values
+  // (as above) and impose the sign.
+  // TODO: Can we do something faster if we HAVE_FAST_INT_OPS?
+
+  uint64_t usx = std::abs (x), usy = std::abs (y);
+  bool positive = (x < 0) == (y < 0);
+
+  // Get upper words
+  uint64_t ux = usx >> 32, uy = usy >> 32;
+  uint64_t res;
+  if (ux)
+    {
+      if (uy) 
+        goto overflow;
+      else
+        {
+          uint64_t ly = static_cast<uint32_t> (usy), uxly = ux*ly;
+          if (uxly >> 32) 
+            goto overflow;
+          uxly <<= 32; // never overflows
+          uint64_t lx = static_cast<uint32_t> (usx), lxly = lx*ly;
+          res = uxly + lxly;
+          if (res < uxly)
+            goto overflow;
+        }
+    }
+  else if (uy)
+    {
+      uint64_t lx = static_cast<uint32_t> (usx), uylx = uy*lx;
+      if (uylx >> 32) 
+        goto overflow;
+      uylx <<= 32; // never overflows
+      uint64_t ly = static_cast<uint32_t> (usy), lylx = ly*lx;
+      res = uylx + lylx;
+      if (res < uylx)
+        goto overflow;
+    }
+  else
+    {
+      uint64_t lx = static_cast<uint32_t> (usx);
+      uint64_t ly = static_cast<uint32_t> (usy);
+      res = lx*ly;
+    }
+
+  if (positive)
+    {
+      if (res > static_cast<uint64_t> (max_val ()))
+        {
+          ftrunc = true;
+          return max_val ();
+        }
+      else
+        return static_cast<int64_t> (res);
+    }
+  else
+    {
+      if (res > static_cast<uint64_t> (-min_val ()))
+        {
+          ftrunc = true;
+          return min_val ();
+        }
+      else
+        return -static_cast<int64_t> (res);
+    }
+
+
+overflow:
+  ftrunc = true;
+  return positive ? max_val () : min_val ();
+
 }
 
-static void
-gripe_64bit_mixed()
+#define INT_DOUBLE_BINOP_DECL(OP,SUFFIX) \
+  template <> \
+  OCTAVE_API octave_ ## SUFFIX \
+  operator OP (const octave_ ## SUFFIX & x, const double& y)
+
+#define DOUBLE_INT_BINOP_DECL(OP,SUFFIX) \
+  template <> \
+  OCTAVE_API octave_ ## SUFFIX \
+  operator OP (const double& x, const octave_ ## SUFFIX & y)
+
+INT_DOUBLE_BINOP_DECL (+, uint64)
+{
+  return (y < 0) ? x - octave_uint64(-y) : x + octave_uint64(y);
+}
+
+DOUBLE_INT_BINOP_DECL (+, uint64)
+{ return y + x; }
+
+INT_DOUBLE_BINOP_DECL (+, int64)
 {
-  (*current_liboctave_error_handler) 
-    ("mixed double - 64-bit integer operations are not implemented");
+  if (fabs (y) < static_cast<double> (octave_int64::max ()))
+    return x + octave_int64 (y);
+  else
+    {
+      // If the number is within the int64 range (the most common case,
+      // probably), the above will work as expected. If not, it's more
+      // complicated - as long as y is within _twice_ the signed range, the
+      // result may still be an integer. An instance of such an operation is
+      // 3*2**62 + (1+intmin('int64')) that should yield int64(2**62) + 1.  So
+      // what we do is to try to convert y/2 and add it twice. Note that if y/2
+      // overflows, the result must overflow as well, and that y/2 cannot be a
+      // fractional number.
+      octave_int64 y2 (y / 2); 
+      return (x + y2) + y2;
+    }
+}
+
+DOUBLE_INT_BINOP_DECL (+, int64)
+{ 
+  return y + x; 
+}
+
+INT_DOUBLE_BINOP_DECL (-, uint64)
+{
+  return x + (-y);
 }
 
-#define DEFINE_DOUBLE_INT64_OP0(OP,ARGT,RETT) \
-  template <> \
-  OCTAVE_API RETT \
-  operator OP (const double&, const ARGT&) \
-  { gripe_64bit_mixed (); return 0.0; } \
-  template <> \
-  OCTAVE_API RETT \
-  operator OP (const ARGT&, const double&) \
-  { gripe_64bit_mixed (); return 0.0; } \
+DOUBLE_INT_BINOP_DECL (-, uint64)
+{
+  if (x <= static_cast<double> (octave_uint64::max ()))
+    return octave_uint64(x) - y; 
+  else
+    {
+      // Again a trick to get the corner cases right. Things like 
+      // 3**2**63 - intmax('uint64') should produce the correct result, i.e.
+      // int64(2**63) + 1.
+      const double p2_64 = std::pow (2.0, 64);
+      if (y.bool_value ())
+        {
+          const uint64_t p2_64my = (~y.value ()) + 1; // Equals 2**64 - y
+          return octave_uint64 (x - p2_64) + octave_uint64 (p2_64my);
+        }
+      else
+        return octave_uint64 (p2_64);
+    }
+}
+
+INT_DOUBLE_BINOP_DECL (-, int64)
+{
+  return x + (-y);
+}
+
+DOUBLE_INT_BINOP_DECL (-, int64)
+{
+  static const bool twosc = (std::numeric_limits<int64_t>::min () 
+                             < -std::numeric_limits<int64_t>::max ());
+  // In case of symmetric integers (not two's complement), this will probably
+  // be eliminated at compile time.
+  if (twosc && y.value () == std::numeric_limits<int64_t>::min ())
+    {
+      return octave_int64 (x + std::pow(2.0, 63));
+    }
+  else
+    return x + (-y); 
+}
 
-#define DEFINE_DOUBLE_INT64_BIN_OP(OP) \
-  DEFINE_DOUBLE_INT64_OP0(OP,octave_int64,octave_int64) \
-  DEFINE_DOUBLE_INT64_OP0(OP,octave_uint64,octave_uint64) 
+// NOTE:
+// Emulated mixed multiplications are tricky due to possible precision loss.
+// Here, after sorting out common cases for speed, we follow the strategy
+// of converting the double number into the form sign * 64-bit integer* 2**exponent,
+// multiply the 64-bit integers to get a 128-bit number, split that number into 32-bit words
+// and form 4 double-valued summands (none of which loases precision), then convert these
+// into integers and sum them. Though it is not immediately obvious, this should work
+// even w.r.t. rounding (none of the summands lose precision).
+
+// Multiplies two unsigned 64-bit ints to get a 128-bit number represented
+// as four 32-bit words.
+static void 
+umul128 (uint64_t x, uint64_t y, uint32_t w[4])
+{
+  uint64_t lx = static_cast<uint32_t> (x), ux = x >> 32;
+  uint64_t ly = static_cast<uint32_t> (y), uy = y >> 32;
+  uint64_t a = lx * ly;
+  w[0] = a; a >>= 32;
+  uint64_t uxly = ux*ly, uylx = uy*lx;
+  a += static_cast<uint32_t> (uxly); uxly >>= 32;
+  a += static_cast<uint32_t> (uylx); uylx >>= 32;
+  w[1] = a; a >>= 32;
+  uint64_t uxuy = ux * uy;
+  a += uxly; a += uylx; a += uxuy;
+  w[2] = a; a >>= 32;
+  w[3] = a;
+}
+
+// Splits a double into bool sign, unsigned 64-bit mantissa and int exponent
+static void 
+dblesplit (double x, bool& sign, uint64_t& mtis, int& exp)
+{
+  sign = x < 0; x = fabs (x);
+  x = frexp (x, &exp);
+  exp -= 52;
+  mtis = static_cast<uint64_t> (ldexp (x, 52));
+}
+
+// Gets a double number from a 32-bit unsigned integer mantissa, exponent and sign.
+static double
+dbleget (bool sign, uint32_t mtis, int exp)
+{
+  double x = ldexp (static_cast<double> (mtis), exp);
+  return sign ? -x : x;
+}
+
 
-DEFINE_DOUBLE_INT64_BIN_OP(+)
-DEFINE_DOUBLE_INT64_BIN_OP(-)
-DEFINE_DOUBLE_INT64_BIN_OP(*)
-DEFINE_DOUBLE_INT64_BIN_OP(/)
+INT_DOUBLE_BINOP_DECL (*, uint64)
+{
+  if (y >= 0 && y < octave_uint64::max () && y == xround (y))
+    {
+      return x * octave_uint64 (static_cast<uint64_t> (y));
+    }
+  else if (y == 0.5)
+    {
+      return x / octave_uint64 (static_cast<uint64_t> (2));
+    }
+  else if (y < 0 || lo_ieee_isnan (x) || lo_ieee_isinf (x))
+    {
+      return octave_uint64 (x.value () * y); 
+    }
+  else
+    {
+      bool sign;
+      uint64_t my;
+      int e;
+      dblesplit (y, sign, my, e);
+      uint32_t w[4];
+      umul128 (x.value (), my, w);
+      octave_uint64 res = octave_uint64::zero;
+      for (short i = 0; i < 4; i++)
+        {
+          res += octave_uint64 (dbleget (sign, w[i], e));
+          e += 32;
+        }          
+      return res;
+    }
+}
+
+DOUBLE_INT_BINOP_DECL (*, uint64)
+{ return y * x; }
 
-#define DEFINE_DOUBLE_INT64_CMP_OP(OP) \
-  DEFINE_DOUBLE_INT64_OP0(OP,octave_int64,bool) \
-  DEFINE_DOUBLE_INT64_OP0(OP,octave_uint64,bool) 
+INT_DOUBLE_BINOP_DECL (*, int64)
+{
+  if (fabs (y) < octave_int64::max () && y == xround (y))
+    {
+      return x * octave_int64 (static_cast<int64_t> (y));
+    }
+  else if (fabs (y) == 0.5)
+    {
+      return x / octave_int64 (static_cast<uint64_t> (4*y));
+    }
+  else if (lo_ieee_isnan (x) || lo_ieee_isinf (x))
+    {
+      return octave_int64 (x.value () * y); 
+    }
+  else
+    {
+      bool sign;
+      uint64_t my;
+      int e;
+      dblesplit (y, sign, my, e);
+      uint32_t w[4];
+      sign = (sign != (x.value () < 0));
+      umul128 (std::abs (x.value ()), my, w);
+      octave_int64 res = octave_int64::zero;
+      for (short i = 0; i < 4; i++)
+        {
+          res += octave_int64 (dbleget (sign, w[i], e));
+          e += 32;
+        }          
+      return res;
+    }
+}
+
+DOUBLE_INT_BINOP_DECL (*, int64)
+{ return y * x; }
+
+DOUBLE_INT_BINOP_DECL (/, uint64)
+{
+  return octave_uint64 (x / static_cast<double> (y));
+}
 
-DEFINE_DOUBLE_INT64_CMP_OP(<)
-DEFINE_DOUBLE_INT64_CMP_OP(<=)
-DEFINE_DOUBLE_INT64_CMP_OP(>)
-DEFINE_DOUBLE_INT64_CMP_OP(>=)
-DEFINE_DOUBLE_INT64_CMP_OP(==)
-DEFINE_DOUBLE_INT64_CMP_OP(!=)
+DOUBLE_INT_BINOP_DECL (/, int64)
+{
+  return octave_int64 (x / static_cast<double> (y));
+}
+
+INT_DOUBLE_BINOP_DECL (/, uint64)
+{
+  if (y >= 0 && y < octave_uint64::max () && y == xround (y))
+    {
+      return x / octave_uint64 (y);
+    }
+  else
+    return x * (1.0/y);
+}
+
+INT_DOUBLE_BINOP_DECL (/, int64)
+{
+  if (fabs (y) < octave_int64::max () && y == xround (y))
+    {
+      return x / octave_int64 (y);
+    }
+  else
+    return x * (1.0/y);
+}
+
+#define INSTANTIATE_INT64_DOUBLE_CMP_OP0(OP,T1,T2) \
+  template OCTAVE_API bool \
+  octave_int_cmp_op::mop<octave_int_cmp_op::OP> (T1 x, T2 y)
+
+#define INSTANTIATE_INT64_DOUBLE_CMP_OP(OP) \
+  INSTANTIATE_INT64_DOUBLE_CMP_OP0(OP, double, int64_t); \
+  INSTANTIATE_INT64_DOUBLE_CMP_OP0(OP, double, uint64_t); \
+  INSTANTIATE_INT64_DOUBLE_CMP_OP0(OP, int64_t, double); \
+  INSTANTIATE_INT64_DOUBLE_CMP_OP0(OP, uint64_t, double)
+
+INSTANTIATE_INT64_DOUBLE_CMP_OP(lt);
+INSTANTIATE_INT64_DOUBLE_CMP_OP(le);
+INSTANTIATE_INT64_DOUBLE_CMP_OP(gt);
+INSTANTIATE_INT64_DOUBLE_CMP_OP(ge);
+INSTANTIATE_INT64_DOUBLE_CMP_OP(eq);
+INSTANTIATE_INT64_DOUBLE_CMP_OP(ne);
+
+#endif
 
 //template <class T>
 //bool
@@ -178,8 +593,6 @@
   template OCTAVE_API octave_int<T> pow (const octave_int<T>&, const double&); \
   template OCTAVE_API octave_int<T> powf (const float&, const octave_int<T>&); \
   template OCTAVE_API octave_int<T> powf (const octave_int<T>&, const float&); \
-  template OCTAVE_API std::ostream& operator << (std::ostream&, const octave_int<T>&); \
-  template OCTAVE_API std::istream& operator >> (std::istream&, octave_int<T>&); \
   template OCTAVE_API octave_int<T> \
   bitshift (const octave_int<T>&, int, const octave_int<T>&); \