将单精度浮点数转换为除法

时间:2015-07-10 23:08:04

标签: c floating-point fortran hpc

作为高性能计算人员,我们倾向于尽可能默认为单精度浮点数(floatreal)。这是因为如果每个操作单独执行得更快,则每秒可以执行更多操作。

然而,与我合作的资深人士之一总是坚持(当需要准确性时),您应该暂时将单精度数据转换为双精度数据以执行除法。那就是:

float a, b;
float ans = ((double)a)/((double)b);

real :: a, b, ans
ans = real(dble(a)/dble(b))

取决于您所使用的语言。在我看来,这看起来非常难看,说实话,我甚至不知道ans中的答案是否会更准确而不是简单地用单点精度写ans = a/b

有人可以告诉我,在算术之前转换你的数字,专门用于执行除法,实际上会得到更准确的答案吗?这是一个语言/编译器特定的问题,还是由IEEE决定?使用什么数值可以最明显地提高准确度?

非常感谢任何有启发性的评论/答案。

4 个答案:

答案 0 :(得分:10)

  

float ans =((double)a)/((double)b);

article表明ans始终与IEEE 754算术和FLT_EVAL_METHOD = 0的单精度除法计算的相同。

当FLT_EVAL_METHOD = 1时,同样的属性也很简单。

当FLT_EVAL_METHOD = 2时,我不确定。有可能人们可能会将规则解释为long double计算a/b必须先计算为double,然后计算为float。在这种情况下,它可能不如直接从long double舍入到float精确(后者产生正确的舍入结果,而前者在极少数情况下可能无法做到,除非另一个定理像菲格罗亚适用并表明这种情况从未发生过。)

长话短说,对于任何现代的,合理的浮点计算平台(*),float ans = ((double)a)/((double)b);有任何好处都是迷信。你应该问你在问题中提到的老年人展示一对a, b的结果不同的值,更不用说更准确了。当然,如果他们坚持认为这样做会更好,那么他们提供一对价值就不会有任何问题。

(*)记得在GCC中使用-fexcess-precision=standard来保持理智

答案 1 :(得分:4)

这在很大程度上取决于所使用的平台。

使用非SSE指令的80x86(或20世纪80年代8087)使用80位精度(long doublereal*10)执行所有算法。它是"商店"将结果从数字处理器移动到失去精度的存储器的指令。

除非它是一个真正的骨头编译器,否则最高精度应来自

float a = something, b = something_else;
float ans = a/b;

由于执行除法,加载后单精度操作数将扩展精度,结果将得到扩展精度。

如果您正在做一些更复杂的事情并希望保持最高精度,请不要将中间结果存储在较小的变量中:

float a, b, c, d;

float prod_ad = a * d;
float prod_bc = b * c;
float sum_both = prod_ad + prod_bc;   // less accurate

由于大多数编译器都会产生能够使所有中间值保持在扩展精度的代码,因此得到的结果不如一次性完成:

float a, b, c, d;

float sum_both = a * d + b * c;   // more accurate

以Eugeniu Rosca的示例程序为基础:

#include "stdio.h"
void main(void)
{
    float a=73;
    float b=19;

    long double a1 = a;
    long double b1 = b;

    float ans1 = (a*a*a/b/b/b);
    float ans2 = ((double)a*(double)a*(double)a/(double)b/(double)b/(double)b);
    float ans3 = a1*a1*a1/b1/b1/b1;
    long double ans4 = a1*a1*a1/b1/b1/b1;

    printf ("plain:  %.20g\n", ans1);
    printf ("cast:   %.20g\n", ans2);
    printf ("native: %.20g\n", ans3);
    printf ("full:   %.20Lg\n", ans4);
}

提供,无论优化级别

plain:  56.716281890869140625
cast:   56.71628570556640625
native: 56.71628570556640625
full:   56.716285172765709289

这表明,对于琐碎的操作,没有太大的区别。但是,将常量更改为更精确的挑战:

float a=0.333333333333333333333333;
float b=0.1;

提供

plain:  37.03704071044921875
cast:   37.037036895751953125
native: 37.037036895751953125
full:   37.037038692721614131

精度差异显示更明显的效果。

答案 2 :(得分:3)

是的,转换为双精度将为您提供更好的准确性(或者,我应该说,精度)。可以说这取决于IEEE,但仅仅是因为IEEE定义了格式和标准。 double本身比float更精确,存储数字和除法。

要回答您的上一个问题,对于较大的a和较小的(小于1)b,这将是最明显的,因为这样您最终会得到一个非常大的商,在此范围内所有浮点数都不太精细。

答案 3 :(得分:1)

在x86(GCC 4.9.3)上运行:

#include "stdio.h"
int main(int arc, char **argv)
{
    float a=73;
    float b=19;

    float ans1 = (a*a*a/b/b/b);
    float ans2 = ((double)a*(double)a*(double)a/(double)b/(double)b/(double)b);
    printf("plain: %f\n", ans1);
    printf("cast:  %f\n", ans2);
    return 0;
}

输出:

plain: 56.716282
cast:  56.716286

Windows计算器中的相同操作返回:

56.716285172765709287068085726782

显然,第二个结果具有更高的准确性。