0

我有一个 sql:

SELECT Sum(Field1), Sum(Field2), Sum(Field1)+Sum(Field2)
FROM Table
GROUP BY DateField
HAVING Sum(Field1)+Sum(Field2)<>0;

问题有时是 field1 和 field2 的总和是这样的值:9.5-10.3,结果是 -0,800000000000001。任何人都可以解释为什么会发生这种情况以及如何解决它?

4

3 回答 3

2

问题有时是 field1 和 field2 的总和是这样的值:9.5-10.3,结果是 -0.800000000000001。任何人都可以解释为什么会发生这种情况以及如何解决它?

为什么会发生这种情况

floatand类型以double2 为基数存储数字,而不是以 10 为基数存储数字。有时,一个数字可以精确地表示为有限位数。

9.5 → 1001.1

有时它不能。

10.3 → 1010.0 1001 1001 1001 1001 1001 1001 1001 1001...

在后一种情况下,数字将四舍五入为最接近的值,可以表示为 a double

1010.0100110011001100110011001100110011001100110011010 base 2
= 10.300000000000000710542735760100185871124267578125 base 10

当减法以二进制完成时,您会得到:

-0.11001100110011001100110011001100110011001100110100000
= -0.800000000000000710542735760100185871124267578125

输出例程通常会隐藏大部分“噪音”数字。

  • Python 3.1 将其舍入为-0.8000000000000007
  • SQLite 3.6 将其四舍五入为-0.800000000000001.
  • printf %g将其四舍五入-0.8

请注意,即使在将值显示为 -0.8 的系统上,它也与double-0.8 的最佳近似值不同,即:

- 0.11001100110011001100110011001100110011001100110011010
= -0.8000000000000000444089209850062616169452667236328125

因此,在任何使用 的编程语言中double,表达式9.5 - 10.3 == -0.8都是错误的。

decimal

对于这样的问题,最常见的答案是“使用十进制算术”。在这个特定的例子中,这确实得到了更好的输出。使用 Python 的decimal.Decimal类:

>>> Decimal('9.5') - Decimal('10.3')
Decimal('-0.8')

但是,您仍然需要处理

>>> Decimal(1) / 3 * 3
Decimal('0.9999999999999999999999999999')
>>> Decimal(2).sqrt() ** 2
Decimal('1.999999999999999999999999999')

这些可能比二进制数字更熟悉的舍入错误,但这并不会使它们变得不那么重要

事实上,二进制分数比具有相同位数的十进制分数更准确,因为以下组合:

它也更快(在 PC 上),因为它具有专用硬件。

十进制并没有什么特别之处。这只是基于我们拥有的手指数量的任意选择。

说新生婴儿重 0x7.5 磅(更熟悉的术语是 7 磅 5 盎司)与说它重 7.3 磅一样准确。(是的,两者之间有 0.2 盎司的差异,但是它在公差范围内。)通常,十进制在表示物理测量方面没有优势。

钱不一样

与测量到一定精度的物理量不同,货币是被计算的,因此是一个精确的数量。怪癖是它以 0.01 的倍数计算,而不是像大多数其他离散量那样以 1 的倍数计算。

如果您的“10.3”真的意味着 10.30 美元,那么您应该使用十进制数字类型来准确表示该值。

(除非您使用 1/16 美元时的历史股票价格,在这种情况下二进制就足够了 ;-))

否则,这只是一个显示问题。

你得到了正确的 15 位有效数字的答案。这对于所有实际目的都是正确的。如果只想隐藏“噪音”,请使用 SQLROUND函数。

于 2010-09-21T04:28:40.867 回答
1

这可能是浮点数实现的影响。有时数字无法准确表示,有时运算结果与我们可能出于相同原因的预期略有不同。

解决方法是对值使用舍入函数来切断无关的数字。像这样(我只是在小数点后四舍五入到 4 位有效数字,但当然您应该使用适合您数据的任何精度):

SELECT Sum(Field1), Sum(Field2), Round(Sum(Field1)+Sum(Field2), 4)
FROM Table
GROUP BY DateField
HAVING Round(Sum(Field1)+Sum(Field2), 4)<>0;
于 2010-09-12T05:11:49.850 回答
1

我确定这是因为浮点数据类型(在 MS Access 中也称为 Double 或 Single)不精确。它不像小数,它是一个简单的以 10 的幂缩放的值。如果我没记错的话,浮点值可以有不同的分母,这意味着它们并不总是精确地转换回以 10 为底的值。

解决方法是将 Field1 和 Field2 从浮点/单/双更改为十进制或货币。如果您给出需要存储的最小值和最大值的示例,包括所需的最小和最大分数,例如 0.0001 或 0.9999,我们可能会为您提供更好的建议。

请注意,2007 年之前的 Access 版本可能会在十进制值上出现 ORDER BY 问题。请阅读这篇文章的评论以获得更多关于此的观点。在许多情况下,这对人们来说不是问题,但在其他情况下可能是。

通常,float 应该用于最终可能非常小或非常大(小于或大于小数可以容纳的值)的值。您需要了解 float 以牺牲一些精度为代价来保持更准确的比例。也就是说,在浮点数可以继续运行的情况下,小数会溢出或下溢。但是浮点数只有有限数量的有效数字,而小数的数字都是有效的。

如果您无法更改列类型,那么同时您可以通过四舍五入您的最终计算来解决该问题。直到最后一刻才四舍五入。

更新

对我使用小数的建议的批评已得到平衡,而不是关于意外的 ORDER BY 结果的重点,但在相同位数的情况下,浮点数总体上更准确。

对这个事实没有异议。但是,我认为人们使用实际上被计算或预计以十为基数表示的值更为常见。我在论坛上一遍又一遍地看到关于他们的浮点数据类型有什么问题的问题,而我没有看到关于十进制的相同问题。这对我来说意味着人们应该从十进制开始,当他们准备好如何以及何时使用浮点数时,他们可以研究它并在他们有能力时开始使用它。

与此同时,虽然当你知道十进制不准确时总是有人推荐它可能有点令人沮丧,但不要让自己与现实世界脱节,现实世界会有更熟悉的舍入误差,但会以略微降低准确性为代价是有价值的。

让我向我的批评者指出,这个例子

Decimal(1) / 3 * 3屈服1.999999999999999999999999999

用熟悉的话来说,就是“正确到 27 位有效数字”,即“对于所有实际目的都是正确的”。

因此,如果我们有两种方法来做实际上相同的事情,并且它们都可以非常精确地表示数字到可笑的有效数字位数,并且都需要四舍五入,但其中一种的舍入误差明显比另外,我不能接受推荐更熟悉的人无论如何都是不好的。初学者如何制作一个可以执行a - a但无法获得0答案的系统?他会感到困惑,并在他试图理解它的同时停止工作。然后他会去留言板上寻求帮助,并被告知 pat 答案“使用小数”。那他就好了又过了五年,直到他长大到有一天会好奇,最后学习并真正掌握浮动在做什么并能够正确使用它。

话虽如此,归根结底我不得不说,抨击我推荐十进制似乎在外太空有点过分。

最后,我想指出以下陈述并非严格正确,因为它过于概括:

float 和 double 类型以 2 为基数存储数字,而不是以 10 为基数存储数字。

准确地说,大多数现代系统都以 2 为基数存储浮点数据类型。但不是全部!有些人使用或使用了以 10 为底。据我所知,有些系统使用更接近e的以 3 为底的系统,因此比以 2 为底的表示具有更优化的基数经济(好像这对所有计算机用户的 99.999% 来说真的很重要) )。此外,说“float 和 double 类型”可能有点误导,因为 double 是 float,但 float 不是 double。Float 是浮点的缩写,但SingleDouble是浮点子类型,表示可用的总精度。还有单扩展和双扩展浮点数据类型。

于 2010-09-12T05:12:23.453 回答