.net 快速整数ABS函数

vawmfj5a  于 2022-12-01  发布在  .NET
关注(0)|答案(8)|浏览(170)
int X = a-b;
int d = Math.Abs(X);

我很确定.NET不支持内联。那么,我会使用if()吗?还是有其他一些不太为人知的技巧?

sxpgvts3

sxpgvts31#

有关http://graphics.stanford.edu/~seander/bithacks.html#IntegerAbs如何在不使用分支的情况下计算绝对值,请参阅www.example.com。
虽然.Net支持内联,但我怀疑编译器是否会考虑将Math.Abs()作为内联的候选对象。

public static int Abs(int value)
{
  if (value >= 0)
    {
      return value;
    }
  return AbsHelper(value);
}

private static int AbsHelper(int value)
{
  if (value == -2147483648)
  {
    throw new OverflowException(Environment.GetResourceString("Overflow_NegateTwosCompNum"));
  }
  return -value;
}

其他整数类型的重载也是类似的。floatdouble重载是外部调用,而decimal重载使用自己的实现,它构造了一个新的示例。哎哟!

n7taea2i

n7taea2i2#

C#会执行内嵌Math.Abs。这是可行的:

int x = 12;
int y = 17;
int z = Math.Abs(x - y);
Console.WriteLine(z); //outputs 5
qlckcl4x

qlckcl4x3#

C#执行内联Math.Abs(),下面是Math.Abs的C#和汇编代码(使用在线工具SharpLab生成):
C#:

public int test(int n){
    return Math.Abs(n);
}

组装:

L0000: push ebp
L0001: mov ebp, esp
L0003: test edx, edx
L0005: jge L000d
L0007: neg edx
L0009: test edx, edx
L000b: jl L0011
L000d: mov eax, edx
L000f: pop ebp
L0010: ret
L0011: call System.Math.ThrowAbsOverflow()
L0016: int3
snvhrwxg

snvhrwxg4#

如果你知道它是关于例如一个最小化问题的差异,你可以使用:a〈b?b-a:a-b

vojdkbi0

vojdkbi05#

我做了一些性能测试,看看除了标准的Math. Abs之外,使用其他工具是否真的可以节省时间。
执行所有这些2000000000次后的结果(i从-1000000000到+1000000000,因此没有溢出):

Math.Abs(i)                    5839 ms     Factor 1
i > 0 ? i : -i                 6395 ms     Factor 1.09
(i + (i >> 31)) ^ (i >> 31)    5053 ms     Factor 0.86

(这些数字因运行不同而略有不同)
基本上你可以得到一个非常轻微的改善比Math.Abs,但没有什么壮观的。
使用bit hack可以节省一点Math.Abs所需的时间,但可读性会受到严重影响。
有了简单的分支,你实际上可以慢一点。总体来说,在我看来不值得。
所有测试均在32位操作系统、Net 4.0、VS 2010、发布模式下运行,未连接调试器。
下面是实际代码:

class Program
{
    public static int x; // public static field. 
                         // this way the JITer will not assume that it is  
                         // never used and optimize the wholeloop away
    static void Main()
    {
        // warm up
        for (int i = -1000000000; i < 1000000000; i++)
        {
            x = Math.Abs(i);
        }

        // start measuring
        Stopwatch watch = Stopwatch.StartNew();
        for (int i = -1000000000; i < 1000000000; i++)
        {
            x = Math.Abs(i);
        }
        Console.WriteLine(watch.ElapsedMilliseconds);

        // warm up
        for (int i = -1000000000; i < 1000000000; i++)
        {
            x = i > 0 ? i : -i;
        }

        // start measuring
        watch = Stopwatch.StartNew();
        for (int i = -1000000000; i < 1000000000; i++)
        {
            x = i > 0 ? i : -i;
        }
        Console.WriteLine(watch.ElapsedMilliseconds);

        // warm up
        for (int i = -1000000000; i < 1000000000; i++)
        {
            x = (i + (i >> 31)) ^ (i >> 31);
        }

        // start measuring
        watch = Stopwatch.StartNew();
        for (int i = -1000000000; i < 1000000000; i++)
        {
            x = (i + (i >> 31)) ^ (i >> 31);
        }
        Console.WriteLine(watch.ElapsedMilliseconds);

        Console.ReadLine();
    }
}
jtw3ybtb

jtw3ybtb6#

JIT在某些情况下执行内联。我不知道它是否内联Math.Abs ......但您是否验证过这实际上对您来说是一个性能问题?在您知道需要微优化之前,不要进行微优化,然后通过以下方式测量性能增益:

int d = X > 0 ? X : -X;

来验证它是否值得。
正如Anthony所指出的,上面的代码(通常)对int.MinValue不起作用,因为-int.MinValue == int.MinValue,而Math.Abs将抛出OverflowException。您也可以在直接的C#中使用检查算法强制执行此操作:

int d = X > 0 ? X : checked(-X);
thtygnil

thtygnil7#

实际上,一个32位带符号的2的补码格式int的绝对值通常是这样实现的:
绝对值(x)=(x^(x〉〉31))-(x〉〉31)

rqenqsqc

rqenqsqc8#

我只看它是否小于零,然后乘以-1

int d = (X < 0) ? (-X) : X;

相关问题