可变位浮点数的乘法和除法

Multiply and divide of variable bit floating point numbers

提问人:ZeunO8 提问时间:4/26/2023 最后编辑:greybeardZeunO8 更新时间:5/18/2023 访问量:414

问:

我正在开发一个允许创建大数(整数和浮点数)的库。您可以在此处找到公共存储库BigNumber

我已经为 和 实现了加法和减法,但是只为BigFloatingBigIntegerBigInteger

浮点数的位存储在 a 中,格式如下:std::vector

sign (1 bit)|binary integer(variable bits)|binary fraction(variable bits)

这样这个数字就会有位5.50 101 1

用这种格式对数字进行乘除有哪些算法?

(5.5)   101 1
* (5.5) 101 1
-------------
= (30.25) 11110 01

(5.5)   101 1
/ (5.5) 101 1
-------------
= (1)     1

要实现的功能位于:BigCommon.cpp

std::tuple<std::vector<bool>, size_t, size_t> BigCommon::multiplyBits(const std::vector<bool> &lhsBits, const std::vector<bool> &rhsBits, const size_t &integerBitsSize, const size_t &mantissaBitsSize)

std::tuple<std::vector<bool>, size_t, size_t> BigCommon::divideBits(const std::vector<bool> &lhsBits, const std::vector<bool> &rhsBits, const size_t &integerBitsSize, const size_t &mantissaBitsSize)

更新

我实现了multiplyBits算法,如下所示:

std::tuple<std::vector<bool>, size_t, size_t> BigCommon::multiplyBits(const std::vector<bool> &lhsBits, const std::vector<bool> &rhsBits, const size_t &integerBitsSize, const size_t &mantissaBitsSize)
{
 std::vector<bool> result;
 result.insert(result.begin(), lhsBits[0] ^ rhsBits[0]);
 size_t newIntegerBitsSize = 0;
 size_t newMantissaBitsSize = mantissaBitsSize + mantissaBitsSize;
 std::vector<bool> lhsBinary(lhsBits.begin() + 1, lhsBits.end());
 std::vector<bool> rhsBinary(rhsBits.begin() + 1, rhsBits.end());
 std::vector<bool> multResult = multiplyBinaryVectors(lhsBinary, rhsBinary);
 newIntegerBitsSize = multResult.size() - newMantissaBitsSize;
 result.insert(result.begin() + 1, multResult.begin(), multResult.end());
 return {result, newIntegerBitsSize, newMantissaBitsSize};
};

现在只是为了分裂!

更新 2

我使用以下算法成功实现了除法:

代码被编辑以支持答案

更新 3

经过一些测试,我发现除法算法不适用于某些类型的数字,以下是一些测试用例: .绝对与5 / 0.2710 / 100divideBinaryVectors

C++ 算法 浮点 二进制

评论

1赞 NathanOliver 4/26/2023
为什么?不应该吗?0 101 10 101 101
1赞 ZeunO8 4/26/2023
0对于符号位(正),对于 5,将 .5 表示为二进制分数1011
1赞 Iłya Bursov 4/26/2023
您将如何以这种格式表示 5.05 或 5.005?
1赞 NathanOliver 4/26/2023
你真的需要在代码中添加注释来解释你在做什么。我仍然不明白您如何/为什么编码为.51
1赞 Wyck 4/26/2023
@NathanOliver == == (1/2) 以同样的方式 == == (1/2)。或者把它想象成你在 2^(-1) 的列中有一个 1。.1b1b / 10b0.5d5d / 10d

答:

4赞 Chris Dodd 4/27/2023 #1

您描述的是一个二进制点表示形式,其大小在固定二进制点的上方和下方都是可变的(无界的)。

对于这种表示形式,乘法非常简单——只需将每个数字的整数位和小数位连接起来,乘以,然后再次拆分为整数/分数。如果输入数字分别具有 jk 小数位,则结果将具有 j+k 小数位。

除法更难,因为当结果不能完全以二进制表示时,您需要决定继续除法的程度(多少额外的小数位)来计算。从根本上说,它只是长除法——将整数/分数位连接起来,除法(一个可能不会终止的过程),然后分割结果。例如,10/5.5 给你

           1.11010 00101 11010 ....
       /----------------------
101.1 / 1010.00000 00000 00000
         101 1
         100 10000 00000 00000
          10 11
           1 11000 00000 00000
           1 011
              1100 00000 00000
              1011
                 1 00000 00000
                   1011
                    1010 00000
                     101 1
                     100 10000
                      10 11
                       1 11000
                       1 011
                          1100
                          1011
                             1

在结果中重复 10 位值。您需要决定何时将其切断并四舍五入分数。

评论

0赞 ZeunO8 4/27/2023
这太棒了!您能否为适用于组合位的长除法提供一些代码?std::vector<bool>
0赞 Hans Olsson 4/28/2023
请注意,一旦您决定将结果四舍五入到某个长度(除法所必需),您还可以将其添加为其他运算(如乘法)的选项。
1赞 greybeard 5/4/2023 #2

我对使用 std::vector<bool> 表示符号和有效位以及size_t表示整数位对大数进行乘除的算法有哪些?

评估速度快于速度慢的最佳建议:
转换为有用的格式,计算并转换回来。

对于计算“in”这种格式:
乘法相对不受数字表示差异的影响。
要知道的算法是长乘法和 Toom,即 Toom-2/Karatsuba

不执行/预检查除法(通常与恢复除法混为一谈)并不比不恢复更费力,因为比较允许提前出局:对于每个商位,平均需要检查大约三个位。

除了缺少规范之外,我发现截至 2023/05/04 的 BigCommon.cpp 实现尝试存在两个问题:

  • 基于参数终止计算的方法非常可疑mantissaBitsSize
  • 假设做正确的事情,使用减法
    无论迭代方向如何,仅计算余数和除数位是没有意义的。
    remainder >= divisor

评论

0赞 greybeard 5/4/2023
(在减法中,我会用来强调右边用中/对称性的等价性。borrow = !left && (borrow || right) || right && borrow
1赞 vvv444 5/5/2023 #3

Boost.Multiprecision 库中的 cpp_bin_float 类可根据需要使用二进制表示形式实现任意大的浮点运算。它还支持无穷大和 NaN 的特殊边缘情况。

除法实现在这里。这有点复杂,但你可以在调试器下运行一个简单的示例代码来理解逻辑。基本上,正如这里已经说过的,对于除法,您只需将数字假定为整数,然后计算分隔小数部分的点的位置。但首先,您必须决定要获得的目标精度。Boost 的实现还在后台使用任意长整数算术。

关于实现的注意事项:
强烈建议避免使用 .更好的选择可能是 boost::d ynamic_bitset
std::vector<bool>

0赞 ZeunO8 5/12/2023 #4

为了对定点二进制数进行除法,我们可以使用以下算法,这是长除法的修改版本,可以正确更新 integerBitsSize 和 fractionBitsSize

std::tuple<std::vector<bool>, size_t, size_t> BigCommon::divideBits(const std::vector<bool> &lhsBits, const std::vector<bool> &rhsBits, const size_t &integerBitsSize, const size_t &fractionBitsSize)
{
 if (isZero(rhsBits))
 {
  throw std::domain_error("Division by zero");
 }

 bool resultSign = lhsBits.front() ^ rhsBits.front();

 std::vector<bool> lhs(lhsBits.begin() + 1, lhsBits.end());
 std::vector<bool> rhs(rhsBits.begin() + 1, rhsBits.end());

 // Normalize dividend and divisor to remove leading zeros
 normalizeBits(lhs);
 normalizeBits(rhs);

 std::vector<bool> dividend = lhs;
 std::vector<bool> divisor = rhs;

 std::vector<bool> result;
 std::vector<bool> tempDividend;

 size_t resultIntegerBitsSize = 0;
 size_t resultFractionBitsSize = 0;

 size_t dividendSize = dividend.size();
 size_t dividendIndex = 0;

 while (true)
 {
  tempDividend.insert(tempDividend.end(), dividendIndex < dividendSize ? dividend[dividendIndex] : false);
  dividendIndex++;
  if (binaryVectorLessThanOrEqualsTo(divisor, tempDividend))
  {
   result.insert(result.end(), true);
   tempDividend = subtractBinaryVectors(tempDividend, divisor);
  }
  else
  {
   result.insert(result.end(), false);
  }
  if (dividendIndex <= dividendSize)
  {
   resultIntegerBitsSize++;
  }
  else
  {
   resultFractionBitsSize++;
   if (resultFractionBitsSize >= 128)
   {
    break;
   }
  }
 }

 while (resultIntegerBitsSize > 0 && !result.front())
 {
  result.erase(result.begin());
  resultIntegerBitsSize--;
 }

 while (resultFractionBitsSize > 0 && !result.back())
 {
  result.erase(result.end() - 1);
  resultFractionBitsSize--;
 }

 result.insert(result.begin(), resultSign);
 return {result, resultIntegerBitsSize, resultFractionBitsSize};
};

它已经在失败的测试用例上进行了测试,并返回了正确的结果。5 / 0.2710 / 100