提问人:Cuco 提问时间:9/10/2023 最后编辑:Cuco 更新时间:9/12/2023 访问量:167
C 和 x86 汇编中的自制“fabs”函数
Homemade 'fabs' function in C and x86 Assembly
问:
我正在尝试在 GNU C 中制作一个返回 32 位浮点数绝对值的 fabs 函数。我有三种不同的方法,分别称为 fabs1、fabs2 和 fabs3:
#include <math.h>
#include <stdio.h>
typedef union
{
float v;
struct
{
int mantissa : 23;
int exponent : 8;
int negative : 1;
} b;
} components;
float fabs1(float f)
{
return f >= 0.0 ? f : -f;
}
float fabs2(float f)
{
components c;
c.v = f;
c.b.negative = 0;
return c.v;
}
float fabs3(float f)
{
double aux = f;
unsigned short cw;
__asm__
(
"finit;\
fstcw %[cw];\
andw $0xf0ff, %[cw];\
orw $0x0200, %[cw];\
fldcw %[cw];\
fldl %[aux];\
fabs;\
fstpl %[aux];"
: [aux] "=mr" (aux) : "m" (aux), [cw] "m" (cw)
);
return aux;
}
void main(void)
{
printf("fabs(-189.55f) = %f\n", fabs(-189.55f));
printf("fabs1(-189.55f) = %f\n", fabs1(-189.55f));
printf("fabs2(-189.55f) = %f\n", fabs2(-189.55f));
printf("fabs3(-189.55f) = %f\n", fabs3(-189.55f));
}
有三个不同的函数,一个使用简单的决策,一个使用联合的复杂一些,最后一个使用 x86 程序集。我正在用 Cygwin 32 位编译它:
C:/Developer/Cygwin/bin/i686-w64-mingw32-gcc -masm=att -I.. -std=c99 -o main.exe main.c
我在 Windows 11 中运行它,结果是:
fabs(-189.55f) = 189.550000
fabs1(-189.55f) = 189.550003
fabs2(-189.55f) = 189.550003
fabs3(-189.55f) = 189.550003
但它们真的应该是:
fabs(-189.55f) = 189.550000
fabs1(-189.55f) = 189.550000
fabs2(-189.55f) = 189.550000
fabs3(-189.55f) = 189.550000
你能看出其中的区别吗?在这三种情况下,我如何摆脱额外的 0.000003?
答:
0赞
Chris Dodd
9/12/2023
#1
导致 出现的基本问题是,数字不能以 -- 最接近的值是(十六进制),当在小数点后打印 6 位数字时,它是3
189.550000
float
189.5500030517578125
0x1.7b199ap+7
189.550003
编译器被允许以更高的精度执行操作,因此当您使用 (可能是内置的并返回 ) 时,您可能会获得值(使用双精度可以得到的最接近的值 -- 以十六进制为单位),但所有手写函数都返回 float 值fabs
double
189.55000000000001136868377216160297393798828125
0x1.7b1999999999ap+7
189.5500030517578125
要摆脱 3,您可以:
- 将一切变为精确
double
- 将输出更改为小数点后 5 个字符(格式为
%.5f
然而,两者都不能解决IEEE二进制浮点数不能精确表示以10为基数的分数的根本问题,因此总是会出现四舍五入和不精确的情况。
评论
2赞
Eric Postpischil
9/12/2023
每次调用 OP 代码中的某个变体都会作为参数传递。这应该始终是相同的值,并且其中一个调用是 to 并且具有参数和返回类型这一事实应该是无关紧要的。 应该在传递给 之前生成一个值,并且传递给 不应更改该值。彼得·科德斯(Peter Cordes)已经发现了问题,即编译器缺陷。fabs
-189.55f
float
fabs
fabs
double
double
-189.55f
float
fabs
fabs
0赞
Chris Dodd
9/12/2023
@EricPostpischil 的精度只是一个最低限度——如果编译器愿意,它总是被允许以更高的精度计算事物,但这不是必需的。但这与OPs为什么出现(以及如何摆脱它)的问题无关,这是由于使用精度。float
3
float
0赞
Eric Postpischil
9/12/2023
C 标准明确规定,相同形式的浮点文字(源文本中的字符完全相同)的所有实例都必须转换为相同的值。C 2018 6.4.4.2 5:“......同一源形式的所有浮点常量应转换为具有相同值的相同内部格式。
0赞
Peter Cordes
9/12/2023
编译器被允许以更高的精度执行操作 - 是的,但这意味着任何操作的起点仍然必须是实际存在的,正如 Eric 所说。该文本的值为 类型 。要获得超出此范围的精度,就需要对 进行解析,并将其更改为将其解析为 ,正如 Eric 所说,这不是标准所说的应该发生的事情。godbolt.org/z/n53nsKhW6 表明,作为 printf 的参数,在尾数中具有预期的低零,但普通没有。189.55f
float
float
float
double
(float)189.55f
189.55f
1赞
Eric Postpischil
9/19/2023
@PeterCordes:呃。糟糕的借口。我预计该标准允许过高精度的原因是为了运行时性能 - 使用一个 FMA 而不是两个指令,在处理器上使用双精度指令而不使用单精度指令,等等。将该标准的计算自由度解释为适用于转换常数是值得怀疑的。我希望大多数有经验的浮点程序员会认为这是指一个特定的值,所以 GCC 的行为会让他们感到惊讶。189.55f
评论
fabs
double
fabs
double
double
-189.55
fabsf
fabs