C++ 浮点加法(从头开始):无法计算负结果

Posted

技术标签:

【中文标题】C++ 浮点加法(从头开始):无法计算负结果【英文标题】:C++ Floating Point Addition (from scratch): Negative results cannot be computed 【发布时间】:2020-12-07 09:01:26 【问题描述】:

我正在实现一个浮点加法程序从头开始,遵循此 PDF 中列出的方法:https://www.cs.colostate.edu/~cs270/.Fall20/resources/FloatingPointExample.pdf

我遇到的主要问题是,当结果为正时(例如 -10 + 12、3 + 5.125),加法有效,但当结果为负时,加法无效。这是因为不明白如何执行以下步骤:

Step 5: Convert result from 2’s complement to signed magnitude
If the result is negative, convert the mantissa back to signed magnitude by inverting the bits and adding 1. The result is
positive in this example, so nothing needs to be done.

如何在不使用浮点加法的情况下确定结果是否为负(我不允许使用任何浮点加法或双加法)?当然,我可以查看当前和下一个浮点数是否为负数并查看它们的累积量,但这会破坏此分配的目的。

如果给定以下内容:

    X 的符号位、指数和尾数 Y 的符号位、指数和尾数 Z 的尾数和指数

如何判断Z = X + Y是否为负数,仅使用上述数据而不使用任何浮点加法?

【问题讨论】:

【参考方案1】:

关键点在于许多浮点格式将符号和尾数分开,因此尾数是一个无符号整数。符号和尾数可以简单地组合以创建一个 signed 整数。然后,您可以使用有符号整数运算来添加或减去浮点数的两个尾数。

【讨论】:

【参考方案2】:

如果您遵循您发布的 PDF,您应该在 步骤 3 将数字转换为 2 的补码。在Step 4 中的加法之后,您将得到 2 的补码结果。 (相加的结果)

要检查结果是否为负,您需要检查结果位模式中最左边的位(符号位)。在 2 的补码中,该位为 1 表示负数,0 表示非负数。

sign = signBit;
if (signBit) 
  result = ~result + 1;

如果您使用无符号整数来保存位模式,您可以将它们设置为固定大小,以便稍后使用移位找到符号位。

uint64_t result;
...
signBit = (result >> 63) & 1;

【讨论】:

result = ~result + 1; 你为什么要写那个而不是result = -result;?在带符号整数的 C 语言中,您在逻辑上处理的是值,而不是位模式。如果您正确编写它(使用一元 -),它甚至可以在使用补码的 C 实现上工作。至少这一部分;必须使用unsigned 来获得 2 的补码加法/减法肯定不太方便,因为 IEEE-754 格式在对指数无偏后基本上具有 2 的补码有符号指数。 由于问题中的文档建议将数字转换为 2 的补码,我考虑了整数,或者我们用来保存结果模式的任何东西,只是作为我们用来保存位模式的对象对应于 2 的补码表示。如果您首先用减号否定数字,那么您肯定需要考虑系统允许使用的任何表示。在这种情况下,a - 将是正确的方法。 第二个甚至不是真正的代码,它只是表明您需要转换回正数。 我现在明白你的意思了,与 0 比较是错误的。更新了答案。 如果您只想使用位模式而不是值,您应该使用无符号,然后result = -result; 在 C 中做正确的事情。C 保证无符号整数是二进制并正常换行(与 2 的补码相同)。没有理由破坏 2 的补码身份来进行否定。如果您 result 已签名,并且您关心不使用 2 的补码的 C 实现的可移植性,我认为使用签名的 + 1 可能是一个补码或符号/大小是不安全的。或~,这是一个补码否定(相当于-result)。【参考方案3】:

在第 5 步,您已经添加了尾数。要确定结果是正数还是负数,只需检查该和的符号位即可。

【讨论】:

问:我如何做 X? A:就做X吧。如果就这么简单,也许OP遗漏了一个细节,或者你是。在这两种情况下,这个答案都需要更多解释。 当您说检查和的符号位时,您的意思是检查尾数的符号位吗? Z 的符号位仍然是第 5 步的默认值。 是的,尾数。这完全决定了结果的符号。【参考方案4】:

小学数学和我们用浮点做的唯一区别是我们有二进制补码(以 2 为底与以 10 为底并没有真正的相关性,只是让生活更轻松)。所以,如果你读完了小学,你就会知道这一切是如何运作的。

在小学的十进制中,您对齐小数点,然后进行数学运算。对于浮点,我们移动较小的数字并丢弃它的尾数(抱歉分数)位以将其与较大的数字对齐。

在小学里,如果做减法,你会在解决身份后从较大的数字中减去较小的数字

a - (-b) = a + b
-a + b = b - a

等等,这样你要么有

n - m 

n + m

然后你做数学。根据获得 a-b 或 a+b 所需的操作来应用该符号。

二进制补码的美妙之处在于否定或否定被反转并添加一个,这很好地融入了逻辑。

a - b = a + (-b) = a + (~b) + 1

因此您无需重新排列操作数,但您可能不得不否定第二个操作数。 此外,您不必记住结果的符号,结果告诉您它的 签名。

所以对齐点 把它放在表格里

a + b  
a + (-b)

其中 a 可以是正数或负数,但 b 的符号和操作可能需要 否定 b。

做加法。

如果结果为负,则将结果取反为正

标准化

IEEE 只涉及让 1.fraction 为正数的愿望,其他浮点格式允许负的 Whole.fraction 并且不求反,简单地说 规范化。剩下的只是小学数学(加上补码)

一些例子


2 + 4

二进制数是

+10
+100

转换为规范化的形式是

+1.0  * 2^1
+1.00 * 2^2

需要相同的指数(对齐点)

+0.10 * 2^2
+1.00 * 2^2

两者都是积极的,所以没有改变只是做加法

这是基本形式,我在前面放了比需要更多的符号扩展 使结果的符号更容易看到。

      0
 000010
+000100
=======

填写

 000000
 000010
+000100
========
 000110

结果为正(结果的 msbit 为零)所以归一化

+1.10 * 2^2

4+5

100
101

+1.00 2^2
+1.01 2^2

相同的指数 都是正面的

      0
 000100
+000101
=======

 001000
 000100
+000101
=======
 001001 

结果为正,所以归一化

+1.001 * 2^3

4 - 2

100
10

+1.00 * 2^2
+1.0  * 2^1

需要相同的指数

+1.00 * 2^2
+0.10 * 2^2

减去 a - b = a + (-b)

     1 <--- add one
 00100
+11101 <--- invert
=======

填写

 11011
 00100
+11101
=======
 00010

结果为正,所以归一化

+1.0 * 2^1

2 - 4

10
100

+1.0 * 2^1
+1.00 * 2^2

做相同的指数

+0.10 * 2^2
+1.00 * 2^2

算一算

a - b = a + (-b)

      1
 000010
+111011
========

填写

 000111
 000010
+111011
========
 111110

结果为负,所以取反 (0 - n)

 000011  <--- add one  
 000000
+000001  <--- invert
=========
 000010

标准化

-1.0 * 2^1

【讨论】:

以上是关于C++ 浮点加法(从头开始):无法计算负结果的主要内容,如果未能解决你的问题,请参考以下文章

长整数加法运算

使用整数运算的浮点加法

js浮点数计算问题 + 金额大写转换

为啥C++里面浮点与整数相乘的结果跟在计算器里的不一样呢?浮点我选的float

408计算机组成原理—加减运算和溢出判断

408计算机组成原理—加减运算和溢出判断