浏览代码

Fixing multiplication and to_string.
Adding a brute-force-like test for multiplying different scales together.

Samuel Jaffe 8 年之前
父节点
当前提交
9edde52a47
共有 3 个文件被更改,包括 71 次插入21 次删除
  1. 1 1
      include/bignum_helper.h
  2. 21 20
      src/bigdecimal.cpp
  3. 49 0
      test/bigdecimal.t.h

+ 1 - 1
include/bignum_helper.h

@@ -11,7 +11,7 @@
 
 namespace math { namespace detail {
   using data_type = std::vector<int32_t>;
-  constexpr const int32_t powers[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
+  constexpr const int32_t powers[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1 };
   // 1 => GREATER, 0 => EQUAL, -1 => LESS
   int compare(data_type const & rhs, data_type const & lhs, size_t offset = 0);
   void add(data_type & rhs, data_type const & lhs, size_t offset = 0);

+ 21 - 20
src/bigdecimal.cpp

@@ -156,7 +156,6 @@ static bool is_one(bigdecimal::data_type const & data, int32_t scale) {
 
 bigdecimal & math::operator*=(bigdecimal & rhs, bigdecimal const & lhs) {
   int32_t const new_scale = rhs.scale_ + lhs.scale_;
-  size_t const offset = mul_scale(rhs.scale_) + mul_scale(lhs.scale_) - mul_scale(new_scale);
   bool is_neg = rhs.is_negative != lhs.is_negative;
   if (rhs == bigdecimal::ZERO || lhs == bigdecimal::ZERO) {
     rhs = bigdecimal::ZERO;
@@ -166,11 +165,11 @@ bigdecimal & math::operator*=(bigdecimal & rhs, bigdecimal const & lhs) {
     rhs = lhs;
     rhs.is_negative = is_neg;
   } else {
-    detail::multiply(rhs.data, lhs.data, offset);
-    int32_t const steps = add_scale(rhs.scale_), nsteps = add_scale(new_scale);
-    int32_t const off1 = (steps <= 0 ? 0 : 1), off2 = (steps < 0 ? 0 : 1);
-    if (steps - off1 > nsteps) {
-      rhs.data.erase(rhs.data.begin(), rhs.data.begin() + steps - nsteps - off1);
+    detail::multiply(rhs.data, lhs.data);
+    int32_t const steps = add_scale(rhs.scale_) + add_scale(lhs.scale_), nsteps = add_scale(new_scale);
+    int32_t const off2 = (steps < 0 ? 0 : 1);
+    if (steps > nsteps) {
+      rhs.data.erase(rhs.data.begin(), rhs.data.begin() + steps - nsteps);
     } else if (steps + off2 < nsteps) {
       rhs.data.insert(rhs.data.begin(), size_t(nsteps-steps-off2), 0);
     }
@@ -224,23 +223,25 @@ bool math::operator==(bigdecimal const & lhs, bigdecimal const & rhs) {
 bigdecimal const bigdecimal::ZERO{0}, bigdecimal::ONE{1}, bigdecimal::NEGATIVE_ONE{-1};
 
 std::string bigdecimal::to_string() const {
-  std::vector<char> output(biginteger::SEG_DIGITS * data.size() + 1, '\0');
-  std::ptrdiff_t idx = 0;
-  if (is_negative) { output[0] = '-'; ++idx; }
-  idx += sprintf(output.data() + idx, "%d", data[data.size()-1]);
-  size_t i = data.size()-1;
-  for (; i > size_t(std::max(0, add_scale(scale_))); --i, idx+=SEG_DIGITS) {
-    sprintf(output.data() + idx, "%0.*d", SEG_DIGITS, data[i-1]);
+  size_t const decimal_split = size_t(std::max(0, add_scale(scale_)));
+  int32_t const hidden = std::max(0, SEG_DIGITS * (-scale_/SEG_DIGITS));
+  size_t const chars = SEG_DIGITS * data.size() + size_t(hidden);
+  std::vector<char> output(chars + 3, '\0');
+  char * ptr = output.data();
+  if (is_negative) { *ptr++ = '-'; }
+  ptr += sprintf(ptr, "%d", data.back());
+  for (size_t i = data.size()-1; i > decimal_split; --i) {
+    ptr += sprintf(ptr, "%0.*d", SEG_DIGITS, data[i-1]);
   }
-  if (scale_ > 0) { // TODO: scale > size?
-    sprintf(output.data() + idx++, ".");
-    for (; i > 1; --i, idx+=SEG_DIGITS) {
-      sprintf(output.data() + idx, "%0.*d", SEG_DIGITS, data[i-1]);
+  if (scale_ > 0) {
+    *ptr++ = '.';
+    for (size_t i = decimal_split; i > 1; --i) {
+      ptr += sprintf(ptr, "%0.*d", SEG_DIGITS, data[i-1]);
     }
     int32_t const val = scale_ % SEG_DIGITS;
-    sprintf(output.data() + idx, "%0.*d", val, data[0]/detail::powers[SEG_DIGITS-val]);
-  } else if (scale_ < 0) {
-    output.insert(output.begin() + idx, size_t(-SEG_DIGITS*add_scale(scale_)), '0');
+    sprintf(ptr, "%0.*d", val, data[0]/detail::powers[SEG_DIGITS-val]);
+  } else {
+    sprintf(ptr, "%0.*d", hidden, 0);
   }
   return output.data();
 }

+ 49 - 0
test/bigdecimal.t.h

@@ -131,6 +131,55 @@ public:
     math::bigdecimal G(100000, -5);
     TS_ASSERT_EQUALS((G*F).to_string(), "1000000000");
   }
+  
+  math::bigdecimal __create(int32_t scale) {
+    if (scale >= 0) return { "1", scale };
+    std::vector<char> data(size_t(std::abs(scale))+3, 0);
+    data[0] = '1';
+    sprintf(data.data()+1, "%0.*d", -scale, 0);
+    return { data.data(), scale };
+  }
+  
+  std::string __createExpect(int32_t scale1, int32_t scale2) {
+    int32_t decimals{0}, magnitude{std::min(scale1, scale2)};
+    if (scale1 < 0 == scale2 < 0) {
+      decimals = scale1 + scale2;
+    } else if (scale1 > 0 && scale1 > -scale2) {
+      decimals = scale1 + scale2;
+    } else if (scale2 > 0 && scale2 > -scale1) {
+      decimals = scale1 + scale2;
+    } else {
+      decimals = std::min(scale1, scale2);
+    }
+    if (decimals < 0) { magnitude = decimals; }
+    std::vector<char> data(size_t(std::abs(decimals) + std::abs(magnitude))+4, 0);
+    char * ptr = data.data();
+    *ptr++ = '1';
+    if (magnitude < 0) {
+      ptr += sprintf(ptr, "%0.*d", -magnitude, 0);
+    }
+    if (decimals > 0) {
+      sprintf(ptr, ".%0.*d", decimals, 0);
+    }
+    return data.data();
+  }
+  
+  void __testMultiplyScales(int32_t scale1, int32_t scale2) {
+    math::bigdecimal A(__create(scale1));
+    math::bigdecimal B(__create(scale2));
+    // This is wrong
+    std::string expected(__createExpect(scale1, scale2));
+    TS_ASSERT_EQUALS((A*B).to_string(), expected);
+    TS_ASSERT_EQUALS((B*A).to_string(), expected);
+  }
+  
+  void testMultiplicationBrutePermutations() {
+    for (int32_t i = -20; i <= +20; ++i) {
+      for (int32_t j = i; j <= +20; ++j) {
+        __testMultiplyScales(i, j);
+      }
+    }
+  }
 
   void testDivideHigherScaleByLowerScale() {
     math::bigdecimal a("1", 2);