我希望从长值中“截断”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/