【问题标题】:double division by using two floats?通过使用两个浮点数进行双重除法?
【发布时间】:2010-11-04 13:21:19
【问题描述】:

我想使用两个浮点数进行双除(Direct Compute 似乎不支持双除)。

这可能吗?

这是我目前尝试过的(c#代码,后面应该是HLSL):

int count = 7;
double value = 0.0073812398871474;
float f1 = (float)value;
float f2 = (float)((value - f1));
float r1 = f1 / count;
float r2 = f2 / count;
double result = (double)r1 + (double)r2;

0,00105446285765182(结果)

0,00105446284102106(正确结果)

这与 f1 中的舍入有关。如果值改为:

 double value = 0.0073812344471474;

那么结果是正确的。

【问题讨论】:

  • 如果 count 是 2 的幂,精度可能会提高。
  • @贾斯汀有趣。这是真的。为什么?不幸的是,计数并不总是二的幂。
  • 如果你没有双除,你有双乘吗?
  • @testalino:浮点数像分数 * 2^(exponent) 一样存储,因此当您执行除以 2 的幂时,它通常可以只调整指数而不会损失任何精度。这只会提高您的 r1r2 精度,而不是原来的 f1f2,它们已经四舍五入以适合浮点数。
  • @Naelin 是的,乘法有效

标签: c# floating-point directx-11 compute-shader


【解决方案1】:

使用浮点除法计算计数的倒数,然后使用 Newton-Raphson 倒数公式将精度提高到整倍。

int count = 7;
double value = 0.0073812398871474;
double r = (double) (1.0f / count); // approximate reciprocal
r = r * (2.0 - count*r); // much better approximation
r = r * (2.0 - count*r); // should be full double precision by now.
double result = value * r;

【讨论】:

  • 是的,如果您不需要正确的舍入,这就是非常正确的答案。 (尽管您可能只需要一个细化步骤就可以逃脱)。
【解决方案2】:

显然,您的算术错误并没有立即清楚。让我把它拼出来。

假设一个 double 有两个部分,大部分和小部分,每个部分的精度大约为 32 位。 (这不完全是双打的工作方式,但它会为我们的目的做。)

一个浮点数只有一个部分。

想象一下,我们一次处理 32 位,但将所有内容保持为双精度:

double divisor = whatever;
double dividend = dividendbig + dividendlittle;
double bigquotient = dividendbig / divisor;

什么是大商?是双倍的。所以它有两个部分。 bigquotient 等于 bigquotientbig + bigquotientlittle。继续:

double littlequotient = dividendlittle / divisor;

同样,littlequotient 是 littlequotientbig + littlequotientlittle。现在我们添加商:

double quotient = bigquotient + littlequotient;

我们如何计算它?商有两个部分。 quotientbig 将设置为 bigquotientbig。 quotientlittle 将设置为 bigquotientlittle + littlequotientbig。 littlequotientlittle 被丢弃。

现在假设您在浮动中执行此操作。你有:

float f1 = dividendbig;
float f2 = dividendlittle;
float r1 = f1 / divisor;

好的,r1 是什么?这是一个浮子。所以它只有一个部分。 r1 是 bigquotientbig。

float r2 = f2 / divisor;

什么是r2?这是一个浮子。所以它只有一个部分。 r2 是 littlequotientbig。

double result = (double)r1 + (double)r2;

将它们加在一起,得到 bigquotientbig + littlequotientbig。 bigquotientlittle 发生了什么事? 您已经失去了 32 位的精度,因此在此过程中出现 32 位的不准确也就不足为奇了。 您根本没有提出在 32 位中逼近 64 位算术的正确算法。

为了计算(big + little)/divisor,你不能简单地做(big / divisor) + (little / divisor)。当您在每个除法期间四舍五入时,该代数规则不适用!

现在清楚了吗?

【讨论】:

  • 这一切都很清楚。甚至在我的问题中也提到了这一点。我想知道怎么做,否则我不会问这个问题。我没有问为什么我的尝试没有成功,因为我已经知道原因了。
  • @testalino:你说问题出在 f1 的计算中。那是不是的问题。问题是 r1 的计算中的舍入。这就是我要指出的。
【解决方案3】:

这可能吗?

可以,只要你:

  • 接受不可避免的精度损失
  • 请记住,并非所有双精度数都适合浮点数

更新

阅读完您的 cmets(要求双精度)后,我的更新答案是:

没有。

【讨论】:

  • 当然不应该有任何精度损失。这就是我使用两个浮点数的原因。如果我愿意接受精度损失,那么我可以只投两个浮点数并进行除法。
  • @testalino: float 是一个圆角类型,特别是 IEEE754 single precision floating point 类型。 .NET 中唯一的十进制非舍入类型是 decimal
  • 我知道,我只想使用浮点数进行精度为 double 的除法。我不想要绝对精度(GPU 没有小数)
【解决方案4】:

那么像

result = value * (double)(1f / (float)count); ?

你只划分了两个浮点数。我的演员阵容比需要的多,但重要的是概念。

编辑:
好的,所以您担心实际值和舍入值之间的差异,对吗?所以只要一遍又一遍地做,直到你做对!

double result = 0;
double difference = value;
double total = 0;
float f1 = 0;
while (difference != 0)
{
    f1 = (float)difference;
    total += f1;
    difference = value - total;
    result += (double)(f1 / count);
}

...但是您知道,简单的答案仍然是“不”。这仍然没有捕捉到所有的舍入错误。根据我的测试,它最多可以将不准确性降低到 1e-17,大约 30% 的时间。

【讨论】:

  • 这与 value * 1.0 / count (双精度)的结果不同。
  • 你是对的;这恰好适用于我测试的内容。傻我。
【解决方案5】:

在评论中,你说:

当然不应该有任何损失 的精度。这就是我使用的原因 两个花车。如果我愿意接受损失 精确,然后我可以投两个 浮动并进行除法。

IEEE-754 single precision 值具有 24 个有效二进制数字。 double precision 值有 53 个有效数字。你甚至不能将一个双精度值表示为两个单精度值而不损失精度,更不用说用这种表示法进行算术运算了。

也就是说,可能只使用双精度和单精度之间的转换、双精度减法/加法和单精度运算进行正确舍入的双精度除法,但如果你真的想做正确的事。您需要实际的 IEEE-754 正确舍入,还是只需要最后一位或两位正确的答案?

【讨论】:

  • 我可能永远不需要完整的双精度。浮动绝对不够好。我已经标记了一个到目前为止有效的答案。
猜你喜欢
  • 1970-01-01
  • 2015-10-22
  • 2017-03-05
  • 1970-01-01
  • 1970-01-01
  • 2013-12-31
  • 2021-07-07
  • 2011-05-07
  • 2012-07-27
相关资源
最近更新 更多