有符号零的最小值和最大值

8

我关注以下情况

min(-0.0,0.0)
max(-0.0,0.0)
minmag(-x,x) 
maxmag(-x,x)

根据维基百科 IEEE 754-2008所述,关于最小值和最大值的定义有些余地,当输入值相等但表示不同的情况下会留下一些余地。特别地:

min(+0,−0)或min(−0,+0)必须产生一个值为零的结果,但可以始终返回第一个参数。

最小值和最大值操作已被定义,但在输入值相等但表示不同的情况下会留下一些余地。特别地:

我进行了一些测试,比较了如下所定义的fminfmaxmin和max

#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指令。

以下是结果(我用来测试的代码已经发布在下面)

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.0,fmax(-0.0,0.0)是否必须等于0.0,还是允许不同的实现对其进行不同的定义?如果这是实现定义的,这是否意味着为确保代码与C标准库(例如来自不同编译器的)的不同实现兼容,必须进行检查,以确定它们如何实现最小值和最大值?
那minmag(-x,x)和maxmag(-x,x)呢?这两者在IEEE 754-2008中都有定义。至少在IEEE 754-2008中,它们是实现定义吗?我从Wikipedia对min和max的评论推断出这些是实现定义的。但据我所知,C标准库没有定义这些函数。在OpenCL中,这些函数被定义为:OpenCL

如果 |x| > |y|,则返回 x;如果 |y| > |x|,则返回 y;否则返回 fmax(x, y)。

如果 |x| < |y|,则返回 x;如果 |y| < |x|,则返回 y;否则返回 fmin(x, y)。

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

Itaninum指令集有minmag和maxmag指令(famin和famax),据我所知(从阅读中),在这种情况下它返回第二个参数。然而,minps和maxps似乎并不是这样做的。奇怪的是,_mm_min_ps(-0.0,0.0)= 0.0,_mm_max_ps(-0.0,0.0)= -0.0。我原以为它们会在两种情况下返回第一个参数或第二个参数。为什么minps和maxps指令被定义成这样呢?
#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\n", c[0], c[1], c[2], c[3]); }
    { float c[4]; _mm_store_ps(c,d4); printf("%f %f %f %f\n", c[0], c[1], c[2], c[3]); }
    
    printf("%f %f %f %f\n", 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\n", 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\n", 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\n", 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也不同。


1
你不觉得你的问题应该有一个更好的标题吗? - Sourav Ghosh
我不确定这个问题是否适合在这里讨论。它更像是一次讨论而不是一个具体的问题。而且为什么CPU指令被实现的方式是如此,答案肯定更适合由开发者-英特尔来回答。实际上,这里包括了三个不同的问题。难道不应该将其分开讨论吗? - too honest for this site
@MikeMB,说得好。所以在 min(-0.0,0.0)max(-0.0,0.0) 的情况下并不重要,但在 minmag(-x,x)maxmag(-x,x) 中确实很重要。 - Z boson
1
@Zboson:不确定+/- 0的情况是否像+/- 3的情况那样重要。如果计算3 +(-3)= 6,那么你的结果显然是垃圾。 (我不确定在双倍精度计算中没有任何符号位的相关性。) - tmyklebu
1
@MikeMB:Kahan有一篇有趣的论文/抱怨,名为“复杂初等函数的分支切割,或者关于无用符号位的大惊小怪”。其中一个观点是,由于实数写成复数时的虚部可以有任意符号位,因此没有必要存在任何复数使得sqrt未定义;sqrt(-1.0 + 0.0i)可以是+i,sqrt(-1.0 - 0.0i)可以是-i。这实际上就是C99的csqrt的工作原理。该论文还指出,这通常会使程序运行更加顺畅。 - tmyklebu
显示剩余16条评论
2个回答

1

为什么不亲自阅读标准呢?维基百科关于IEEE的文章包含了该标准的链接。

注意:C语言的标准文档并不免费。但是,最终草案是可以免费获取的(我提供的链接就是最终草案,搜索即可找到pdf版本)。然而,我在这里没有看到过最终文档被引用,据我所知只有一些错别字得到了纠正;没有做出任何更改。IEEE标准是免费的。

请注意,编译器不必遵循标准(例如,一些嵌入式编译器/版本不实现符合IEEE标准的浮点数值,但仍符合C标准——详见标准)。因此,请查看编译器文档以了解兼容性。例如,MS-VC甚至不兼容C99(也永远不会),而gcc和clang/llvm在当前版本中(gcc自4.9.2起至少如此,在部分方面自4.7起)大多与C11兼容。

通常情况下,在使用MS-VC时,需要检查它是否实际上支持所有使用的标准特性。它实际上并不完全符合当前标准,也不符合C99。


我刚刚查看了草稿,其中一条脚注说:“理想情况下,fmax应该对零的符号敏感,例如fmax(-0.0,+0.0)将返回+0; 但是,在软件实现中可能是不切实际的。” - Z boson
C++ 中是否定义了 fminfmax 函数? - Z boson
这是针对C语言的。C++是一种不同的语言。请自行查找答案或提出新问题。(请先自行搜索) - too honest for this site
1
我认为很多人对这两种语言都很熟悉,它们之间的重叠远远大于分歧。既然它们更多地达成一致,而不是分歧,那么在大多数情况下说它们不同是否有点过于拘泥于细节?做一些搜索可以发现,在这两种语言中,“fmin”和“fmax”的定义完全相同,但我并不是专家。 - Z boson
我也不想辩论这个问题。我想我应该同时使用C++标签。 - Z boson
显示剩余11条评论

-2
在这种情况下的根本问题是实际的基础数学问题,忽略表示问题。你的问题中有几个我认为是错误的含义。-0.0<0.0是错误的。-0.0是负数也是错误的。0.0是正数也是错误的。实际上,不存在-0.0,尽管有一个带有符号位的IEEE 754零表示。
此外,min/max函数的行为只是可以产生具有不同符号位的零的合法浮点操作的一小部分。由于浮点单位可以自由地返回(-)0.0,例如像-7 - -7这样的表达式,因此您还必须想出如何处理它。我还想指出,|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)。
无论何种计算目的,在值为零时是否设置或清除符号位都是没有用的。

1
你忘记了在零上数学上未定义的操作,但是当从正端接近零和从负端接近零时,它们有不同的极限。这适用于一些标准数学库函数,对除法来说也有一定程度的影响:在浮点运算中,除以零会产生无穷大,其符号取决于零的符号。 - user743382
同意“-0.0是负数是假的”,由IEEE 754支持,使用sqrt(-0.0)不会导致异常。我认为它甚至返回-0.0(带符号位的零)。关于“计算目的,担心符号位设置还是清除是无用的”,请参考什么操作和+0.0和-0.0上的函数会产生不同的算术结果? - chux - Reinstate Monica
据我所知,在 C 和 C++ 中,零除是未定义的行为。 - MikeMB
1
@MikeMB 是和不是。在规范的必须部分中,对于整数和浮点数除法都明确未定义,但在规范的可选部分中,对于浮点数除法进行了定义,并且该可选部分的规范具有特征检测宏。因此,在使用该特征检测宏的严格符合规范的程序中,可以通过除以零来实现。 - user743382
这里的基础数学是带有符号零的浮点算术。它与您成长过程中接触到的具有无限范围的实分析一样重要。它有自己的一套规则。例如,浮点算术不是结合律的。在这些规则中,带符号的零具有特定的含义(例如,atan(+-0,-1) = +-pi)。顺便提一下,计算机整数算术也不具有无限范围,并且基于模算术有其自己的一套规则。 - Z boson
显示剩余13条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接