首页 > 解决方案 > 否定浮点变量的不同方法生成不同的程序集

问题描述

我有以下代码:

public static float F(float n) 
{
    return -n;
}

生成以下asm

Program.F(Single)
    L0000: push eax
    L0001: vzeroupper
    L0004: vmovss xmm1, [esp+8]
    L000a: vmovss xmm0, [Program.F(Single)]  ; remember this line
    L0012: vxorps xmm0, xmm0, xmm1           ; remember this line
    L0016: vmovss [esp], xmm0
    L001b: fld st, dword ptr [esp]
    L001e: pop ecx
    L001f: ret 4

另一方面,我有以下片段:

public static float G(float n) 
{
    return n * -1;
}

生成:

Program.G(Single)
    L0000: push eax
    L0001: vzeroupper
    L0004: vmovss xmm0, [esp+8]
    L000a: vmulss xmm0, xmm0, [Program.G(Single)] ; remember this line
    L0012: vmovss [esp], xmm0
    L0017: fld st, dword ptr [esp]
    L001a: pop ecx
    L001b: ret 4

问题

笔记

NOTE用于最后一个问题。

我想基本上得到像“C”这样的程序集。例如:

float 
f(float n) {
        return -n; 
}

float
g(float n) {
        return n * -1;
}

生成:

f:
        xorps   xmm0, XMMWORD PTR .LC0[rip]
        ret
g:
        xorps   xmm0, XMMWORD PTR .LC0[rip]
        ret
.LC0:
        .long   -2147483648
        .long   0
        .long   0
        .long   0
public static unsafe float F(float n) 
{
    uint i = *(uint*)&n;
    
    i ^= 0x80000000;
    
    n = *(float*)&i;
    
    return n;
}

我得到了以下我认为效率更低的内容:

Program.F(Single)
    L0000: sub esp, 8
    L0003: vzeroupper
    L0006: mov eax, [esp+0xc]
    L000a: mov [esp], eax
    L000d: mov eax, [esp]
    L0010: xor eax, 0x80000000
    L0015: mov [esp], eax
    L0018: vmovss xmm0, [esp]
    L001d: vmovss [esp+0xc], xmm0
    L0023: vmovss xmm0, [esp+0xc]
    L0029: vmovss [esp+4], xmm0
    L002f: fld st, dword ptr [esp+4]
    L0033: add esp, 8
    L0036: ret 4

现在我们有了一个“高效”的汇编代码:

public static float F(float n) 
{
    return -n;
}
    
public static float G(float n) 
{
    return n * -1;
}
C.F(Single)
    L0000: vzeroupper
    L0003: vmovss xmm1, [C.F(Single)]
    L000b: vxorps xmm0, xmm0, xmm1
    L000f: ret

C.G(Single)
    L0000: vzeroupper
    L0003: vmulss xmm0, xmm0, [C.G(Single)]
    L000b: ret

但我认为第一个和第二个问题是有效的:

为什么我们在这里有不同的 ASM 输出?

标签: c#assemblyfloating-pointx86-64

解决方案


输出不同,原因很简单,编译器不像人类那样“思考”而是遵循标准。

public static float F(float n) 
{
    return -n;
}

这意味着,要求编译器否定 n并返回否定值。
这正是编译器所做的

vxorps xmm0, xmm0, xmm1    <--- change sign bit

public static float G(float n) 
{
    return n * -1;
}

这意味着进行乘法运算,而这正是编译器所做的

vmulss xmm0, xmm0, [C.G(Single)] <--- multiply

在您的世界(-n) == (n * -1)中,但编译器对此有不同的看法。而且这两种表达方式不一样。所以(-n) != (n * -1)和汇编输出是不同的。

对于浮点/双精度值来说,这甚至更糟(a * b * c) != (c * b * a)……

至少不是默认情况下,您可以使用特殊的编译器标志使其相等,抱歉不记得它们到底是什么。


推荐阅读