这些操作是等价的吗?

本文关键字:操作 | 更新日期: 2023-09-27 18:11:36

我正在重构一些代码,我偶然发现了这样一段。

if (a > 1.0)
  a = 1.0;
else if (a < -1.0)
  a = -1.0;

根据我们得到的指导方针,我应该把它重构成这样的东西。

if (Math.Abs(a) > 1.0)
  a = a < 0 ? -1.0 : 1.0;
当然,只有在不改变语句逻辑的情况下才允许重构。我仔细考虑过了,没有发现任何偏差或不符之处。我还编写并运行了一些测试,试图找出那些棘手和棘手的情况。一切似乎都很好。

然后,一个同事偷看了一眼,用一种非常恶毒的语气说那里有什么东西很痒。他本来应该在第二天公布这个大秘密的,但后来他病了,现在正在度假。

我已经盯着这行好几天了。我已经考虑过了。我已经试过了所有的办法。没有什么!所以要么是我看不懂要么就是他是个玩弄卑鄙把戏的混蛋。

这些操作是等价的吗?

是的,它们是一样的。更新:但不是Int.MinValue ' Long.MinValue等情况,因为Math.Abs会抛出OverflowException -感谢@SledgeHammer/@Quantic!-所以这可能是陷阱。

方法相同的"证明"(除去溢出)

if (Math.Abs(a) > 1.0)
  a = a < 0 ? -1.0 : 1.0;

等于:(?: to if)

if (Math.Abs(a) > 1.0)
{
    if (a < 0)
    {
        a = -1.0;
    }
    else
    {
        a = 1.0;
    }
}

,这与:(写出Math.Abs)

相同
if (a > 1.0 || a < -1.0)
{
    if (a < 0)
    {
        a = -1.0;
    }
    else
    {
        a = 1.0;
    }
}

,这与:(将||翻译为if - else if)

相同。
if (a > 1.0)
{
    if (a < 0)
    {
        a = -1.0;
    }
    else
    {
        a = 1.0;
    }
}
else if (a < -1.0)
{
    if (a < 0)
    {
        a = -1.0;
    }
    else
    {
        a = 1.0;
    }
}

删除死代码:

if (a > 1.0)
{
    if (a >= 0) //else
    {
        a = 1.0;
    }
}
else if (a < -1.0)
{
    if (a < 0)
    {
        a = -1.0;
    }
}

,现在删除不需要的(内部)if s:

if (a > 1.0)
{
    a = 1.0;
}
else if (a < -1.0)
{
   a = -1.0;
}

:完成)

PS:如果您喜欢,请使用

if (a > 1.0)
  a = 1.0;
else if (a < -1.0)
  a = -1.0;

更新:数学。Abs

from Math.Abs(a) > 1.0 to a > 1.0 || a < -1.0

Math.Abs(a) > 1.0

Math.Abs(a)等价于(参见参考来源)

a >= 0 ? a : -a 

一样
if (a >= 0)
{
    return a;
}
else
{
    return -a;
}

所以添加了条件:

if (a >= 0)
{
    return a > 1.0;
}
else
{
    return (-a) > 1.0;
}

重写条件:

if (a >= 0)
{
    return a > 1.0;
}
else
{
    return a < -1.0;
}

else对于(a >= 0)不为真,所以

a > 1.0 || a < -1.0

Julian的回答说明了为什么它们在概念上是相同的,所以我将深入研究。

我写了一个小测试程序,然后查看了反汇编(Visual Studio 2012)

class Program
{
    static void A(double a)
    {
        if (a > 1.0)
            a = 1.0;
        else if (a < -1.0)
            a = -1.0;
    }
    static void B(double a)
    {
        if (Math.Abs(a) > 1.0)
            a = a < 0 ? -1.0 : 1.0;
    }
    static void Main(string[] args)
    {
        A(-1.17);
        B(-1.17);
    }
}

A

            if (a > 1.0)
0000002b  fld         qword ptr [ebp+8] 
0000002e  fld1 
00000030  fcomip      st,st(1) 
00000032  fstp        st(0) 
00000034  jp          0000003A 
00000036  jb          0000003E 
00000038  jmp         0000003A 
0000003a  xor         eax,eax 
0000003c  jmp         00000043 
0000003e  mov         eax,1 
00000043  test        eax,eax 
00000045  sete        al 
00000048  movzx       eax,al 
0000004b  mov         dword ptr [ebp-3Ch],eax 
0000004e  cmp         dword ptr [ebp-3Ch],0 
00000052  jne         0000005C 
                a = 1.0;
00000054  fld1 
00000056  fstp        qword ptr [ebp+8] 
00000059  nop 
0000005a  jmp         00000092 
            else if (a < -1.0)
0000005c  fld         qword ptr [ebp+8] 
0000005f  fld         dword ptr ds:[001D2F50h] 
00000065  fcomip      st,st(1) 
00000067  fstp        st(0) 
00000069  jp          0000006F 
0000006b  ja          00000073 
0000006d  jmp         0000006F 
0000006f  xor         eax,eax 
00000071  jmp         00000078 
00000073  mov         eax,1 
00000078  test        eax,eax 
0000007a  sete        al 
0000007d  movzx       eax,al 
00000080  mov         dword ptr [ebp-3Ch],eax 
00000083  cmp         dword ptr [ebp-3Ch],0 
00000087  jne         00000092 
                a = -1.0;
00000089  fld         dword ptr ds:[001D2F58h] 
0000008f  fstp        qword ptr [ebp+8] 

总共38条指令

B

            if (Math.Abs(a) > 1.0)
0000002b  fld         qword ptr [ebp+8] 
0000002e  sub         esp,8 
00000031  fstp        qword ptr [esp] 
00000034  call        749B481F 
00000039  fstp        qword ptr [ebp-44h] 
0000003c  fld         qword ptr [ebp-44h] 
0000003f  fld1 
00000041  fcomip      st,st(1) 
00000043  fstp        st(0) 
00000045  jp          0000004B 
00000047  jb          0000004F 
00000049  jmp         0000004B 
0000004b  xor         eax,eax 
0000004d  jmp         00000054 
0000004f  mov         eax,1 
00000054  test        eax,eax 
00000056  sete        al 
00000059  movzx       eax,al 
0000005c  mov         dword ptr [ebp-3Ch],eax 
0000005f  cmp         dword ptr [ebp-3Ch],0 
00000063  jne         0000008A 
                a = a < 0 ? -1.0 : 1.0;
00000065  fld         qword ptr [ebp+8] 
00000068  fldz 
0000006a  fcomip      st,st(1) 
0000006c  fstp        st(0) 
0000006e  jp          00000072 
00000070  ja          0000007A 
00000072  nop 
00000073  fld1 
00000075  fstp        qword ptr [ebp-4Ch] 
00000078  jmp         00000083 
0000007a  fld         dword ptr ds:[001D3008h] 
00000080  fstp        qword ptr [ebp-4Ch] 
00000083  nop 
00000084  fld         qword ptr [ebp-4Ch] 
00000087  fstp        qword ptr [ebp+8]

总共:36个指令+对Math的函数调用。Abs

结果:

第一个可能稍微快一点,但是它们的大小非常接近,很难想象使用其中一个会严重影响性能的情况。我个人同意你的原始版本在概念上更容易理解的评论。

Edit看起来,多亏了上面的其他评论,主要的区别在于异常是否可以从Math.Abs抛出或被原始版本吞噬。看起来你使用的是double,但是文档没有提到double版本上的异常,就像int版本一样。我仍然支持你原来的版本

为什么不

a = Math.Min(Math.Abs(a), 1.0) * Math.Sign(a);

假设您有FPU支持Math操作,由于干净的管道和更好的分支预测,上述操作可能执行得更快…因为没有分支,因为我们删除了所有的if语句。当然,这完全取决于硬件,并且您无法通过检查IL看到代码大小的减少。您只需要"知道"这是更好的。在某些情况下。

把这个告诉你的老师以获得额外的学分:)