我有一堆浮点数(Java双打),其中大多数非常接近1,我需要将它们乘以较大计算的一部分。我需要这样做 很多.

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

0.0000000000000000000000000000000001 (1.0E-34)

他们不能表示类似:

1.0000000000000000000000000000000001

因此,我迅速失去了精度(对于Java的双打似乎约为1.000000000000001)。

我已经考虑过将数字存储在1中,例如1.0001将存储为0.0001-但问题是要再次将它们乘以将它们倍增,我必须添加1,此时我失去了精度。

为了解决这个问题,我可以使用bigdecimals执行计算(转换为bigdecimal,添加1.0,然后倍增),然后然后转换回双打,但是我对此的性能含义非常严重。

谁能看到这样做的方法可以避免使用BigDecimal?

为了清楚起见,编辑: :这是用于使用梯度下降优化算法的大规模协作过滤器。准确性是一个问题,因为协作过滤器通常会处理很少的数字(例如,一个人单击产品的广告的概率,即1000分之一,或10000分之一)。

速度是一个问题,因为协作过滤器必须在数千万数据点(即使不是更多)上进行培训。

有帮助吗?

解决方案

是的:因为

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

在您的情况下 xy 很小,所以 x*y 将是 远的 较小 - 太小而无法影响您的计算结果。因此,就您而言

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

这意味着您可以减去1个数字,而不是乘以乘以它们。只要结果始终远小于1,它们就足够接近数学上精确的结果,您不会关心差异。

编辑: :只是注意到:你说 最多 它们中的非常接近1。显然,这种技术对不接近1的数字不起作用 - 也就是说,如果 xy 很大。但是,如果一个人很大并且一个很小,那么它可能仍然有效。您只关心产品的大小 x*y. 。 (如果两个数字都不接近1,则可以使用常规Java double 乘法...)

其他提示

也许您可以使用对数?

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

另外,要注意初始的精度损失,有函数log1p(至少在C/C ++中存在),它返回日志(1+X)而没有任何精度损失。 (例如,log1p(1e-30)为我返回1E-30)

然后,您可以使用ExpM1获取实际结果的小数部分。

这种情况不是确切的bigdecimal是为了什么?

编辑以添加:

“根据第二段段落,出于绩效原因,我希望避免避免进行大事。” - 理智

“过早优化是万事万物的根源” - 诺斯

实际上,有一个简单的解决方案来解决您的问题。您担心它可能还不够快,所以您想做一些复杂的事情 思考 会更快。诺斯语言有时会被过度使用,但这正是他警告的情况。以简单的方式写。测试它。个人资料。看看它是否太慢。如果是 然后 开始考虑使其更快的方法。在您知道有必要之前,不要添加所有这些额外的复杂,容易出现的代码。

根据数字的来源以及您的使用方式,您可能需要使用理性而不是浮子。不是所有情况下的正确答案,而是 正确的答案确实没有其他。

如果理性不适合,我会认可对数答案。

响应您的编辑:

如果您要处理代表低响应率的数字,请执行科学家的工作:

  • 将它们表示为过剩 /赤字(标准化1.0部分)
  • 扩展它们。考虑“百万零件”或任何合适的东西。

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

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

我建议您在您认为对您的速度不够快之前测试BigDecimal的性能。您仍然可以使用BigDecimal进行数万计算。

正如大卫指出的那样,您只需添加偏移即可。

(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)

ans =

      1.00004231009302

但是,如果我们只执行简单的添加近似,请查看会发生什么。

1+(x+y+z+w)

ans =

            1.00004231

我们失去了可能很重要的低订单位。这只是一个问题,如果产品中的某些差异至少是SQRT(EPS),其中EPS是您正在工作的精度。

反试试:

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

结果= f(x,y);

结果= f(结果,z);

结果= f(结果,w);

1+结果

ans =

      1.00004231009302

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

如果您真的需要精度,即使它慢于双人,也必须使用诸如bigdecimal之类的东西。

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top