2

我有一堆浮点数(Java 双精度数),其中大部分非常接近 1,我需要将它们相乘作为更大计算的一部分。我需要做很多

问题是,虽然 Java 的双打对像这样的数字没有问题:

0.0000000000000000000000000000000001 (1.0E-34)

他们不能代表类似的东西:

1.0000000000000000000000000000000001

因此,我迅速失去了精度(Java 的双打的限制似乎在 1.000000000000001 左右)。

我考虑过只存储减去 1 的数字,因此例如 1.0001 将存储为 0.0001 - 但问题是再次将它们相乘我必须加 1,此时我失去了精度。

为了解决这个问题,我可以使用 BigDecimals 来执行计算(转换为 BigDecimal,加 1.0,然后相乘),然后再转换回双精度数,但我非常担心这会对性能产生影响。

任何人都可以看到避免使用 BigDecimal 的方法吗?

为清楚起见进行编辑:这是针对采用梯度下降优化算法的大规模协同过滤器。准确性是一个问题,因为协同过滤器通常处理非常小的数字(例如,一个人点击产品广告的概率,可能是千分之一,或万分之一)。

速度是一个问题,因为协同过滤器必须在数千万个数据点上进行训练,如果不是更多的话。

4

8 回答 8

12

是的:因为

(1 + x) * (1 + y) = 1 + x + y + x*y

在您的情况下,x并且y非常小,因此x*y会小得多 -太小而无法影响您的计算结果。所以就你而言,

(1 + x) * (1 + y) = 1 + x + y

这意味着您可以存储减去 1 的数字,而不是相乘,只需将它们相加即可。只要结果总是远小于 1,它们就会足够接近数学上精确的结果,您不会关心差异。

编辑:刚刚注意到:你说它们中的大多数都非常接近 1。显然,这种技术不适用于不接近 1 的数字——也就是说,如果xy很大。但是,如果一个大一个小,它可能仍然有效;你只关心产品的大小x*y。(如果两个数字都不接近 1,您可以使用常规 Javadouble乘法...)

于 2009-04-04T23:03:55.450 回答
11

也许你可以使用对数?

对数方便地减少乘法到加法。

此外,为了处理初始精度损失,还有函数 log1p(至少,它存在于 C/C++ 中),它返回 log(1+x) 而没有任何精度损失。(例如 log1p(1e-30) 为我返回 1e-30)

然后你可以使用 expm1 得到实际结果的小数部分。

于 2009-04-04T23:11:03.437 回答
3

这种情况不正是 BigDecimal 的用途吗?

编辑添加:

“根据倒数第二段,出于性能原因,如果可能,我宁愿避免使用 BigDecimals。” – 理智

“过早的优化是万恶之源”——Knuth

有一个简单的解决方案实际上是为您的问题定制的。您担心它可能不够快,因此您想做一些您认为会更快的复杂操作。Knuth 的名言有时会被过度使用,但这正是他所警告的情况。用简单的方法写出来。测试一下。剖析它。看看是不是太慢了。如果是,那么开始考虑如何让它更快。在您知道有必要之前,不要添加所有这些额外的复杂、容易出错的代码。

于 2009-04-04T23:04:17.900 回答
1

根据数字的来源和使用方式,您可能希望使用有理数而不是浮点数。不是所有情况的正确答案,但当它正确答案时,真的没有其他答案了。

如果有理数不合适,我会支持对数答案。

编辑以响应您的编辑:

如果您正在处理代表低响应率的数字,请按照科学家的做法:

  • 将它们表示为过剩/赤字(标准化 1.0 部分)
  • 缩放它们。以“百万分之几”或任何适当的方式思考。

这将使您处理合理的计算数字。

于 2009-04-04T23:54:06.767 回答
1

值得注意的是,您正在测试硬件而不是 Java 的限制。Java 在您的 CPU 中使用 64 位浮点。

我建议您先测试 BigDecimal 的性能,然后再假设它对您来说不够快。您仍然可以使用 BigDecimal 每秒进行数万次计算。

于 2009-04-05T08:24:28.723 回答
1

正如大卫指出的那样,您可以将偏移量相加。

(1+x) * (1+y) = 1 + x + y + x*y

然而,选择退出最后一个学期似乎是有风险的。不。例如,试试这个:

x = 1e-8 y = 2e-6 z = 3e-7 w = 4e-5

什么是 (1+x) (1+y) (1+z)*(1+w)?在双精度下,我得到:

(1+x) (1+y) (1+z)*(1+w)

答案=

      1.00004231009302

但是,看看如果我们只做简单的加法近似会发生什么。

1 + (x+y+z+w)

答案=

            1.00004231

我们丢失了可能很重要的低阶位。仅当产品中与 1 的某些差异至少为 sqrt(eps) 时,这才是一个问题,其中 eps 是您正在使用的精度。

试试这个:

f = @(u,v) u + v + u*v;

结果 = f(x,y);

结果 = f(结果,z);

结果 = f(结果,w);

1+结果

答案=

      1.00004231009302

如您所见,这使我们回到了双精度结果。事实上,它更准确一些,因为 result 的内部值为 4.23100930230249e-05。

于 2009-04-07T15:32:49.940 回答
0

如果你真的需要精度,你将不得不使用 BigDecimal 之类的东西,即使它比 Double 慢。

如果您真的不需要精确度,您也许可以选择大卫的答案。但即使你经常使用乘法,也可能是一些过早的优化,所以无论如何 BIgDecimal 可能是要走的路

于 2009-04-04T23:11:53.123 回答
0

当您说“其中大多数非常接近 1”时,到底有多少?

也许您可以在所有数字中隐含 1 的偏移量,并且只使用分数。

于 2009-04-05T00:02:36.543 回答