灵感来自“只有40亿个花车 - 所以全部测试”我写了下面的代码:
#include<stdio.h>
#include<limits.h>
#include<math.h>
int main(){
unsigned int i = 0;
float f = 0;
float r = 1;
for(i = 0; i < UINT_MAX ; i++){
f = *(float *)&i;
r = pow(sin(f),2)+pow(cos(f),2);
if (fabs(r-1)> 1/pow(2.0,20)){
printf("hex value 0x%.8X bad value 0x%.8X \n",
*(unsigned int *)&f,*(unsigned int *)&r);
}
else {
printf("hex value 0x%.8X good value 0x%.8X \n",
*(unsigned int *)&f,*(unsigned int *)&r);
}
}
return 0;
}
我运行它(实际上只有几秒钟和OpenMP的并行版本),然后我输出了输出。每千个花车大约有1个失败!
math.h有多好?
我正在使用MacOS High Sierra。
[edit]澄清:
在阅读了评论和答案之后,我谦卑地承认我的问题不是很好,并且反映了C(2000 loc)非常环保。为了澄清,我正在使用C编译器(对于sinf与sin注释)并且代码被“清理”以在此处发布。 在阅读答案之后,我会认为任何挥之不去的不稳定都是由于我的错误造成的。以下仅供参考,是我的新代码,其中包含您的一些建议:
/usr/local/Cellar/gcc/7.2.0/bin/g++-7 -gdwarf-3 -Wall -Wshadow -g -DDEBUG -O0 floattest.c -o test.out -lm -fopenmp
#include<stdio.h>
#include<limits.h>
#include<math.h>
#include<omp.h>
int main(){
unsigned int i = 0;
float f = 0;
float r = 1;
#pragma omp parallel for private(i)
for(i = 0; i < UINT_MAX ; i++){
f = *(float *)&i;
r = pow(sinf(f),2)+pow(cosf(f),2);
if (fabs(r-1)> 1/pow(2.0,5)){
printf("hex value 0x%.8X. Dec value %.10e. Bad value 0x%.8X \n",
*(unsigned int *)&f,f,*(unsigned int *)&r);
}
}
return 0;
}
答案 0 :(得分:3)
您正在尝试调查单精度浮点数量违反三角标识$\sin^2 x + \cos^2 x = 1$
(抱歉,SO没有MathJax)的频率和频率。让我展示一个更好的方法:
sinf
和cosf
,而不是sin
和cos
; pow[f](x, 2)
来平方x,因为x*x
会更快更准确(除非编译器可以优化pow[f](x,2)
到x*x
,这不是你应该依赖的东西。)nextafter
和ldexp
而不是类型 - 惩罚 - 除了不破坏类型别名规则,这很有可能更快; 以下是我的表现。 (注意:使用OpenMP,OpenMP 4.5阵列缩减以及一大堆C99功能。)
#include <float.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <omp.h>
#include <stdio.h>
#include <string.h>
#if FLT_RADIX != 2 || FLT_MIN_EXP != -125 || FLT_MAX_EXP != 128 || !defined INFINITY
#error "This program assumes IEEE-compliant binary floating point"
#endif
// Including subnormals but *not* including infinities and NaNs, IEEE
// singles can have exponents in the range [-149, 127]. Add two more
// for exact zero and overflow.
#define FLT_FULL_EXP_RANGE 279
#define FLT_FULL_EXP_BIAS 148
static inline void
bin_error_in_trig_equality(float x, unsigned long *error_hist)
{
float s, c;
int elog;
s = sinf(x);
c = cosf(x);
elog = ilogbf(1.0f - (s*s + c*c));
if (elog == FP_ILOGB0)
error_hist[0]++;
else if (elog == INT_MAX)
error_hist[FLT_FULL_EXP_RANGE - 1]++;
else
error_hist[elog + FLT_FULL_EXP_BIAS]++;
}
int
main(void)
{
// uint32_t is enough, because we are distributing fewer than
// 2**32 values over these bins.
uint32_t error_hist[FLT_FULL_EXP_RANGE] = { 0 };
// Zero must be tested as a special case.
bin_error_in_trig_equality(0.0f, error_hist);
// Coarse grained parallel exhaustive test: each thread is responsible
// for scanning the full mantissa range for one value of the exponent.
// This covers all finite values except zero.
#pragma omp parallel for reduction(+:error_hist)
for (int i = -149; i <= 127; i++) {
float lo = ldexpf(1.0f, i);
float hi = ldexpf(1.0f, i+1);
for (float x = lo; x < hi; x = nextafterf(x, hi)) {
bin_error_in_trig_equality(x, error_hist);
bin_error_in_trig_equality(-x, error_hist);
}
}
// Dump the histogram in a convenient format for graphing.
printf("bin,count\n");
if (error_hist[0])
printf("0.0,%"PRIu32"\n", error_hist[0]);
for (int i = 1; i < FLT_FULL_EXP_RANGE; i++) {
if (error_hist[i])
printf("2^%d,%"PRIu32"\n", i - FLT_FULL_EXP_BIAS, error_hist[i]);
}
return 0;
}
当我运行这个程序时(在x86-64上,使用GNU libc提供的数学库 2.24),这是它的输出,手动对齐列:
bin, count
0.0, 3633564531
2^-24, 553412596
2^-23, 91212952
[来自Eric Postpischil]使用Apple LLVM 8.1.0,macOS 10.12.6和MacBookPro13,3运行相同的源代码:
bin count
0.0, 3744628459
2^-24, 431565466
2^-23, 101996154
错误始终存在于此并非意外 2 -24 或2 -23 箱;那些对应于IEEE单精度尾数的最低有效两位。
该表的解释是:对于所有可能的有限单精度值的大约85%,标识是准确的;对于另外13%左右,错误不超过一unit in the last place(通常缩写为&#34; ulp&#34;);对于剩余的2.1%的值,误差不超过4个ulps。
我认为这些是通用数学库的可接受错误率。 Transcendental functions are intrinsically hard to compute accurately;你需要使用任意精度数学来使每个值完美地舍入(并且工业级计算机代数引擎将完全实现这一点)。
顺便提一下,这个程序在当前一代只需要12秒的挂钟时间,而不是基于英特尔的顶级笔记本电脑,所以观察到&#34; There are only four billion floats, test them all&# 34;自2014年以来,它变得更加真实。
答案 1 :(得分:1)
macOS中使用的sin
和cos
的来源是here。 1 (注意:开头时有关这些是初步版本的评论会出现过时。)他们在数学上精确的结果的几个ULP内返回结果。鉴于此,它们的平方和应始终在1的ULP内。在此标度下,double
ULP为2 -53 。因此,不应该出现大于2 -20 的错误。
实际上,当我使用Apple LLVM 8.1.0编译问题中的源代码并使用macOS 10.12.6在MacBookPro13,3上运行时,不会报告任何错误值。
因此,我认为OP错了;他们在这个问题中显示的源代码不是产生他们所说的失败的源代码。
1 我写了它们。
答案 2 :(得分:0)
每千个花车中大约有1个失败!
这些是Not-A-Numbers。
许多float
位模式通常代表Not-A-Number。那些float
预计会“糟糕”。
sin(), cos() pow()
的质量因平台而异。质量较差的功能会导致更“糟糕”。
一个好的平台将有0个真正的坏,如下所示。
测试代码重新处理以处理各种缺点。
#include<stdio.h>
#include<limits.h>
#include<math.h>
int main(void) {
unsigned good = 0;
unsigned bad = 0;
unsigned nan = 0;
double ep = 1 / pow(2.0, 20);
union {
unsigned i;
float f;
} u = { 0 };
do {
float f = u.f;
float r = (float) (pow(sin(f), 2) + pow(cos(f), 2));
if (fabs(r - 1) <= ep) {
good++;
} else if (fabs(r - 1) > ep) {
printf("%.20e\n", r);
fflush(stdout);
bad++;
} else {
nan++;
}
} while (++u.i);
printf("good:%u bad:%u nan:%u\n", good, bad, nan);
return 0;
}
我的结果
good:4278190080 bad:0 nan:16777216
因此,大约1/255是NaN。
请注意,OP使用double
函数而不是sinf(), cosf(),...
同样提到的@Eric Postpischil和here,OP的代码使用double
进行所有计算。因此,只有非常弱的<math.h>
函数才会出现“坏”结果,因为double
提供了额外的精度。
重新编写保险float
数学导致以下内容。这将是对OP原始FP测试目标的更好评估。好sinf()
和cosf()
预计是1.0 ULP中最好的答案。 OP方程的计算误差不应该与此相差甚远。
int main(void) {
unsigned good = 0;
unsigned bad = 0;
unsigned nan = 0;
union {
unsigned i;
float f;
} u = { 0 };
do {
float f = u.f;
if (isnan(f)) {
nan++;
} else {
float s = sinf(f);
float ss = powf(s, 2);
float c = cosf(f);
float cc = powf(c, 2);
float r = ss + cc;
if (r == 1) {
good++;
} else if (r > 1 || r < 1) {
static double rmax = 0.0;
if (fabs(r - 1) > rmax) {
rmax = fabs(r - 1);
printf("f:%.10e r:%.10e\n", f, rmax);
fflush(stdout);
}
bad++;
}
}
} while (++u.i);
printf("good:%u bad:%u nan:%u\n", good, bad, nan);
return 0;
}
输出。由于精度更高,许多结果都是“糟糕”,但 不好。
f:2.4414065410e-04 r:5.9604644775e-08
f:1.8584646285e-02 r:1.1920928955e-07
good:3730369702 bad:547820378 nan:16777214