关于C ++:最小和最大有符号零

Minimum and maximum of signed zero

我担心以下情况

1
2
3
4
min(-0.0,0.0)
max(-0.0,0.0)
minmag(-x,x)
maxmag(-x,x)

根据维基百科,IEEE 754-2008关于最小和最大

The min and max operations are defined but leave some leeway for the case where the inputs are equal in value but differ in representation. In particular:

min(+0,?0) or min(?0,+0) must produce something with a value of zero but may always return the first argument.

我做了一些测试,比较fminfmax,最小值和最大值,如下所示

1
2
3
4
5
6
7
8
#define max(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a > _b ? _a : _b; })

#define min(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a < _b ? _a : _b; })

_mm_min_ps_mm_max_ps调用SSE minpsmaxps指令。

结果如下(我用来测试此代码的代码发布在下面)

1
2
3
4
5
6
fmin(-0.0,0.0)       = -0.0
fmax(-0.0,0.0)       =  0.0
min(-0.0,0.0)        =  0.0
max(-0.0,0.0)        =  0.0
_mm_min_ps(-0.0,0.0) =  0.0
_mm_max_ps(-0.0,0.0) = -0.0

如您所见,每种情况都会返回不同的结果。所以我的主要问题是C和C ++标准库怎么说? fmin(-0.0,0.0)是否必须等于-0.0fmax(-0.0,0.0)是否必须等于0.0?或者允许不同的实现方式对其进行不同的定义吗?如果定义了实现,是否意味着要确保代码与C标准库的不同实现(例如来自不同的编译器)兼容,必须进行检查以确定它们如何实现min和max?

minmag(-x,x)maxmag(-x,x)呢?这些都在IEEE 754-2008中定义。是否至少在IEEE 754-2008中定义了这些实现?我从Wikepdia关于min和max的评论中推断出,这些是实现定义的。但是据我所知,C标准库并未定义这些功能。在OpenCL中,这些功能定义为

maxmag Returns x if | x| > |y|, or y if |y| > |x|, otherwise fmax(x, y).

minmag Returns x if |x| < |y|, or y if |y| < |x|, otherwise fmin(x, y).

x86指令集没有minmag和maxmag指令,因此我必须实现它们。但就我而言,我需要性能,并且为幅度相等的情况创建分支效率不高。

Itaninum指令集具有minmag和maxmag指令(faminfamax),在这种情况下,据我所知(通过读取),在这种情况下,它返回第二个参数。这不是minpsmaxps似乎正在做的事情。 _mm_min_ps(-0.0,0.0) = 0.0_mm_max_ps(-0.0,0.0) = -0.0很奇怪。我希望他们在这两种情况下都返回第一个参数,或者第二个返回第二个参数。为什么minpsmaxps指令以这种方式定义?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <stdio.h>
#include <x86intrin.h>
#include <math.h>

#define max(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a > _b ? _a : _b; })


#define min(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a < _b ? _a : _b; })


int main(void) {
    float a[4] = {-0.0, -1.0, -2.0, -3.0};  
    float b[4] = {0.0, 1.0, 2.0, 3.0};
    __m128 a4 = _mm_load_ps(a);
    __m128 b4 = _mm_load_ps(b);
    __m128 c4 = _mm_min_ps(a4,b4);
    __m128 d4 = _mm_max_ps(a4,b4);
    { float c[4]; _mm_store_ps(c,c4); printf("%f %f %f %f
"
, c[0], c[1], c[2], c[3]); }
    { float c[4]; _mm_store_ps(c,d4); printf("%f %f %f %f
"
, c[0], c[1], c[2], c[3]); }

    printf("%f %f %f %f
"
, fmin(a[0],b[0]), fmin(a[1],b[1]), fmin(a[2],b[2]), fmin(a[3],b[3]));
    printf("%f %f %f %f
"
, fmax(a[0],b[0]), fmax(a[1],b[1]), fmax(a[2],b[2]), fmax(a[3],b[3]));

    printf("%f %f %f %f
"
, min(a[0],b[0]), min(a[1],b[1]), min(a[2],b[2]), min(a[3],b[3]));
    printf("%f %f %f %f
"
, max(a[0],b[0]), max(a[1],b[1]), max(a[2],b[2]), max(a[3],b[3]));    
}
//_mm_min_ps: 0.000000, -1.000000, -2.000000, -3.000000
//_mm_max_ps: -0.000000, 1.000000, 2.000000, 3.000000
//fmin: -0.000000, -1.000000, -2.000000, -3.000000
//fmax: 0.000000, 1.000000, 2.000000, 3.000000
//min: 0.000000, -1.000000, -2.000000, -3.000000
//max: 0.000000, 1.000000, 2.000000, 3.000000

编辑:

关于C ++,我测试了std::min(-0.0,0.0)std::max(-0.0,0.0),两者都返回了-0.0。这表明std::minfmin不同并且std::maxfmax不同。


为什么不自己阅读标准? IEEE的Wikipedia文章包含该标准的链接。

注意:C标准文档不是免费提供的。但是最终草案是(这就是我链接的内容,搜索以找到pdf版本)。但是,我还没有看到这里引用的最终文档,并且AFAIK大部分都纠正了一些错别字。没有改变。但是,IEEE是免费提供的。

请注意,编译器不必遵守标准(例如,某些嵌入式编译器/版本未实现符合IEEE的浮点值,但仍符合C语言-只需阅读标准即可了解详细信息)。因此,请参阅编译器文档以了解兼容性。例如,MS-VC甚至与C99不兼容(并且永远都不会变本加厉),而gcc和clang / llvm(在大多数情况下)与当前版本中的C11兼容(至少从4.9.2版开始,gcc从4.7版开始)。

通常,在使用MS-VC时,请检查它是否确实支持所使用的所有标准功能。它实际上不完全符合当前标准,也不完全符合C99。


在这种情况下,基本问题是实际的基础数学,而忽略了表示问题。我认为您的问题中有几个含义是错误的。 -0.0 <0.0为假。 -0.0是负数是假的。 0.0为正数则为假。实际上,虽然存在IEEE 754表示为零且带有符号位的情况,但实际上没有-0.0这样的东西。

此外,最小/最大函数的行为只是合法浮点运算的一小部分,可以产生具有不同符号位的零。由于浮点单位可以自由地为表达式-7--7返回(-)0.0,因此您还必须弄清楚该怎么做。我还要指出的是| 0.0 |实际上-可能会在设置了符号位的情况下返回0.0,因为-0.0是绝对值0.0。简而言之,就数学而言,0.0是-0.0。他们是一样的东西。

可以使用设置的符号位测试0.0的唯一方法是放弃数学表达式,而是检查此类值的二进制表示形式。但是,这有什么意义呢?我只能想到一种合理的情况:从两台不同的机器生成二进制数据,这些机器必须逐位相同。在这种情况下,您还需要担心信令和安静的NaN值,因为这些值还有很多别名(单精度浮点数的10 ^ 22-1 SNaN和10 ^ 22 QNaN的别名,大约10 ^双精度各取51个值)。

在这些情况下,二进制表示很关键(绝对不是数学计算),那么您将必须编写代码以在写入时调节所有浮点数(零,安静的NaN和发出信号的NaN)。

对于任何计算目的,不必担心符号位是置位还是零值清零。