简体   繁体   中英

Fastest way to get remaining count to next ceiling ten in C#

I'm doing a operation in a very large set of numbers (13 digit length each).

I need to validate each number with checksum. Checksum digit will tell how far number is it to next ten, so ie:

checksum    checksum digit
      20                 0
      21                 9
      22                 8
      33                 7
      34                 6
      35                 5
      36                 4
      37                 3
     208                 2
       9                 1

The numbers are EAN-13 format. So, the maximum digits sum = 217 (999999999999: no checksum validation).

By far, the fastest way I have think is preloading data into a int array and retrieving it by index.

Is this the fastest way possible?

Or at this point this does not matter anymore, since it will be executed fast enough even to a lot of numbers?

UPDATE:

preloading values of checksum digit for cheksum into an array as I mentioned:

for (int i = 0; i < 220; i += 10)
{
    matchValues[i] = 0;
    matchValues[i + 1] = 9;
    matchValues[i + 2] = 8;
    matchValues[i + 3] = 7;
    matchValues[i + 4] = 6;
    matchValues[i + 5] = 5;
    matchValues[i + 6] = 4;
    matchValues[i + 7] = 3;
    matchValues[i + 8] = 2;
    matchValues[i + 9] = 1;
}

With this I can cover all checksums with matching checksum digit matchValues[sum];

So:
matchValues[208] = 2;
matchValues[9] = 1;
etc.

You can use the modulo to get the distance from the ceiling ten. You still would need to iterate over each number obviously.

int modulo = i % 10;
int distanceFromTen = modulo == 0 ? 0 : 10 - modulus;

Another solution would be int distanceFromTen = (int)(Math.Ceiling(i / 10d) * 10 - i); .

I've run benchmarks for both approaches:

private static void Main(string[] args)
{
    //Console.WriteLine("Checking {0}", i);

    int loops = 10;

    long averageModulo = 0;
    long averageCeiling = 0;

    for (int l = 0; l < loops; l++)
    {

        Stopwatch sw = new Stopwatch();

        sw.Start();
        for (int i = 0; i < 10000000; i++)
        {
            int modulus = i % 10;
            int distanceFromTen = modulus == 0 ? 0 : 10 - modulus;
        }
        sw.Stop();


        Stopwatch swTwo = new Stopwatch();

        swTwo.Start();

        for (int i = 0; i < 10000000; i++)
        {
            int distanceFromTenTwo = (int)(Math.Ceiling(i / 10d) * 10 - i);
        }

        swTwo.Stop();

        Console.WriteLine("Modulo:       {0} ({1}ms)", sw.ElapsedTicks, sw.ElapsedMilliseconds);

        averageModulo += sw.ElapsedTicks;

        Console.WriteLine("Math.Ceiling: {0} ({1}ms)", swTwo.ElapsedTicks, swTwo.ElapsedMilliseconds);

        averageCeiling += swTwo.ElapsedTicks;

        Console.WriteLine("");
    }

    Console.WriteLine("Average modulo:  {0}", averageModulo / loops);
    Console.WriteLine("Average ceiling: {0}", averageCeiling / loops);

    Console.ReadLine();
}

The modulo operation is always faster than the ceiling (might be because of the boxing). This being said, both operations are very fast.


With the new edit I think this is now aimed at generating valid EANs in as little time as possible. Here is some code that will generate 100000000 EAN-13 checksums (as documented on the Wikipedia page) in toughly 3.5 seconds.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

internal class Program
{
    private static void Main(string[] args)
    {
        Console.WriteLine("");

        long start = 0;
        long end = 99999999;

        long count = end - start + 1;
        long[] eans = new long[count];

        Stopwatch sw = new Stopwatch();

        sw.Start();

        Parallel.For(start, end + 1, i => {
            eans[i] = GenerateEAN13(i);
        });

        sw.Stop();

        Console.WriteLine("Generation of {0} EAN-13s took {1} ticks ({2} ms)", count, sw.ElapsedTicks, sw.ElapsedMilliseconds);

        Console.ReadLine();
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static long GenerateEAN13(long number)
    {
        long checksum = 0;
        long digit = 0;
        long tmp = number;

        for (int i = 13; i >= 0; i--)
        {
            digit = tmp % 10;
            tmp = tmp / 10;

            checksum += i % 2 == 0 ? digit * 3 : digit;

            if (tmp < 10)
                break;
        }

        long modulus = checksum % 10;
        checksum = modulus == 0 ? 0 : 10 - modulus;

        return number * 10 + checksum;
    }
}

Definitely use modulus for this. Your "cache" of values won't help much because the cost saved in the subtraction will be incurred in the lookup of the dictionary value by key, which in turn will call GetHashCode() and have some overhead:

int distanceFromNextTen = (10 - input % 10) % 10;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM