我有一个 single,我相信 C++ 的等效项是 Excel 工作簿模块中 VBA 中的 float。无论如何,当我在 VBA 上设置断点时,我最初分配的值 (876.34497) 在立即窗口中被四舍五入为 876.345,观察和悬停工具提示。但是,如果我将此 Single 传递给 C++ DLL,C++ 会将其报告为原始值 876.34497。

那么,它实际上是作为原始值存储在内存中的吗?这是调试器的一些限制吗?不确定这里发生了什么。很难测试我传递的内容是否是我在 C++ 方面得到的内容。

我试过了:

?CStr(test)
876.345
?CDbl(test)
 876.344970703125
?CSng(test)
 876.345

VBA 不是很简单,所以在某种程度上它必须在内存中存储为 876.34497。否则,我认为 CDbl 不会像现在这样正确。

最佳答案

“单一”类型的 VBA 变量存储为“IEEE 754[-]1985 [sic] 的 32 位硬件实现”。 [见: https://msdn.microsoft.com/en-us/library/ee177324.aspx]

这在英语中的意思是,“单”精度数字被转换为二进制,然后被截断以适应 4 字节(32 位)序列。确切的过程在 http://en.wikipedia.org/wiki/Single-precision_floating-point_format 下的维基百科中有很好的描述。结果是所有单精度数都表示为

(1) a 23 bit "fraction" between 0 and 1, *times*
(2) an 8-bit exponent which represents a multiplier between 2^(-127) and 2^128, *times*
(3) one more bit for positive or negative.

将数字转换为二进制并返回的过程会导致两种类型的舍入错误:

(1) 有效数字——如您所见,有效数字是有限制的。一个 22 位整数只能有 8,388,607 个唯一值。换句话说,任何数字都不能以大于 +/- 0.000012% 的精度表示。回到高中科学,您可能还记得这是另一种说法,您不能指望超过 6 个有效数字(嗯,十进制数字,至少……当然,您有 22 个有效二进制数字)。因此,任何超过六位有效数字的数字表示都将被四舍五入。但是,它不会四舍五入到最接近的十进制数字……它将四舍五入到最接近的二进制数字。这通常会导致一些意想不到的结果(比如你的)。

(2) 二进制转换——另一种错误更加有害。有一些数字明显少于六位(十进制)数字将被四舍五入。例如,十进制的 1/5 是 0.2000000。它永远不会“四舍五入”。但是二进制中的相同数字是 0.00110011001100110011.... 永远重复。 (该序列相当于 1/8 + 1/16 + 1/16*(1/8+1/16) + 1/256*(1/8+1/16) ... )如果您使用任意代表 0.20 的二进制数字的数量,然后将其转换回十进制,您将永远不会得到准确的 0.20。例如,如果您使用 8 位,则二进制数为 0.00110011,即:
  0.12500000
  0.06250000
  0.00781250
+ 0.00390625
------------
  0.19921875

无论您使用多少个二进制数字,您永远不会得到 0.20,因为 0.20 不能表示为 2 的幂之和。

简而言之,这解释了正在发生的事情。当您将 876.34497 分配给“测试”时,它会在内部转换为:
1 10001000 0110110001011000010011
     136       5,969,427

即 (+1) * 2^(136-127) * (5,969,427)/(2^23)

Excel 会自动截断您的单精度数字的显示以仅显示六位有效数字,因为它知道第七位数字可能是错误的。我不能告诉你这个数字到底是什么,因为我的 excel 没有显示足够的有效数字!但你明白了。

当您将值强制转换为 double 时,它会使用整个二进制字符串,然后在末尾添加另外 4 个字节的零。它现在允许您显示两倍的有效数字,因为它是 double 的,但正如您所看到的,从 8 位十进制数字到 23 位二进制数字的转换,然后附加另一长串零引入了一些错误。如果您了解它在做什么,则不是真正的错误;只是文物。毕竟,它完全按照你的吩咐去做……你只是不知道你要它做什么!

10-08 05:13