c# - 从 long 中截断 0 代替 % 10 的更快方法

标签 c# modulo

我希望从长值中“截断”0。

例如,对于长值“1234560000”,我想删除最后四个 0(到 123456)并且还需要知道删除了多少个 0。

我可以通过 % 10 个操作来实现:

void Main()
{
    Console.WriteLine(Truncate(1234560000));
}

public static long Truncate(long mantissa)
{
    int droppedZeros = 0;

    while (mantissa % 10 == 0)
    {
        mantissa /= 10;
        droppedZeros++;
    }

    return mantissa;
}

这段代码被调用了数百万次并且对性能至关重要,我正在寻找提高性能的方法以在没有模数的情况下实现相同的效果(这可以通过位移来完成吗?)。

根据请求,我添加了一些基准测试数字,包括执行除法的基准测试,编译时间已知常量,以展示模运算的相对开销:

          Method |      Mean |     Error |    StdDev |    Median | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
---------------- |----------:|----------:|----------:|----------:|------------:|------------:|------------:|--------------------:|
  DivideNoModulo |  1.863 ms | 0.0431 ms | 0.1272 ms |  1.855 ms |           - |           - |           - |                   - |
     ModuloBasic | 21.342 ms | 0.8776 ms | 2.5876 ms | 20.813 ms |           - |           - |           - |                   - |
   DivisionBasic | 18.159 ms | 1.7218 ms | 5.0768 ms | 15.937 ms |           - |           - |           - |                   - |
 DivisionSmarter |  7.510 ms | 0.5307 ms | 1.5649 ms |  7.201 ms |           - |           - |           - |                   - |
   ModuloSmarter |  8.517 ms | 0.1673 ms | 0.2886 ms |  8.531 ms |           - |           - |           - |                   - |
  StringTruncate | 42.370 ms | 1.7358 ms | 5.1181 ms | 40.566 ms |   1000.0000 |           - |           - |           8806456 B |

基准代码:

 [SimpleJob(RunStrategy.ColdStart, 1)]
    [MemoryDiagnoser]
    public class EncoderBenchmark
    {
        private long _mantissa;

        [Benchmark]
        public void DivideNoModulo()
        {
            for (var i = 0; i < 100000; i++)
            {
                _mantissa = 12345600000000;

                _mantissa /= 100000000;
            }
        }

        [Benchmark]
        public void ModuloBasic()
        {
            for (var i = 0; i < 100000; i++)
            {
                _mantissa = 12345600000000;

                while (_mantissa % 10 == 0)
                {
                    _mantissa /= 10;
                }
            }
        }

        [Benchmark]
        public void DivisionBasic()
        {
            for (var i = 0; i < 100000; i++)
            {
                _mantissa = 12345600000000;

                for (;;)
                {
                    long temp = _mantissa / 10;
                    if (temp * 10 != _mantissa)
                        break;

                    _mantissa = temp;
                }
            }
        }


        [Benchmark]
        public void DivisionSmarter()
        {
            for (var i = 0; i < 100000; i++)
            {
                _mantissa = 12345600000000;


                for (; ; )
                {
                    long temp = _mantissa / 1000000;
                    if (temp * 1000000 != _mantissa)
                        break;

                    _mantissa = temp;
                }

                for (; ; )
                {
                    long temp = _mantissa / 10;
                    if (temp * 10 != _mantissa)
                        break;

                    _mantissa = temp;
                }
            }
        }

        [Benchmark]
        public void ModuloSmarter()
        {
            for (var i = 0; i < 100000; i++)
            {
                _mantissa = 12345600000000;

                while (_mantissa % 1000000 == 0)
                {
                    _mantissa /= 1000000;
                }

                while (_mantissa % 10 == 0)
                {
                    _mantissa /= 10;
                }
            }
        }

        [Benchmark]
        public void StringTruncate()
        {
            for (var i = 0; i < 100000; i++)
            {
                _mantissa = 12345600000000;

                _mantissa = long.Parse(_mantissa.ToString().TrimEnd('0'));
            }
        }
    }

最佳答案

您不太可能通过移位使其高效工作,因为这是除以 2 的幂的理想选择,10 不是 1。 p>

改进的可能性是使用多个循环以更大的 block 来完成工作,例如:

if (mantissa != 0) {
    while (mantissa % 1000000 == 0) {
        mantissa /= 1000000;
        droppedZeros += 6;
    }
    while (mantissa % 1000 == 0) {
        mantissa /= 1000;
        droppedZeros += 3;
    }
    while (mantissa % 10 == 0) {
        mantissa /= 10;
        droppedZeros++;
    }
}

这通常会导致执行的指令较少,但与所有优化一样,测量,不要猜测!它可能是增加的代码复杂性不值得您获得的 yield (如果有的话)。


请注意,我遇到了 mantissa == 0 情况,因为这会导致您的原始代码出现无限循环。


您可能要考虑的另一种可能性是,如果您多次对相同 项目执行此操作。例如,假设您有一组整数,每次需要处理其中一个整数时,您都必须剥离并计算尾随零。

在那种情况下,您实际上可以用不同的方式存储它们。例如,考虑(伪代码)结构:

struct item:
    int originalItem
    int itemWithoutZeroes
    int zeroCount

基本上,每当您第一次收到一个项目(例如 1234560000)时,您会立即将其转换为一次且仅一次的结构:

{ 1234560000, 123456, 4 }

这提供了零剥离项目的缓存版本,因此您无需再次计算它。

因此,如果您想要去除尾数,只需使用 item.itemWithoutZeros。如果要以原始形式输出数字,可以使用 item.originalItem。而且,如果您想要零的计数,请使用 item.zeroCount

显然,这会占用更多的存储空间,但您通常会发现优化是一种时间/空间的权衡。

关于c# - 从 long 中截断 0 代替 % 10 的更快方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54546319/

相关文章:

unix-timestamp - 将模运算符 (%) 与 unix 纪元时间戳一起使用是否安全?

c++ - 如何在不使用堆栈或数组的情况下从 int 中提取每个数字? (C++)

python - 将 python 列表增加 mod x_i 的量,其中 x_i 取决于位置

c# - TCPClient收不到数据

c# - 如何检查是否已抛出任何异常?

c# - 如何处理paypal电子支票

C# ModInverse 函数

C# - DataGridView - 同一行上的图像和文本

c# - 申请表未结束

javascript - 如何使 5 mod 5 = 5 而不是零?