views:

212

answers:

9

I want a number that would be unique forever, I came up with the following code, it generates a number and adds a check digit to the end of it, I would like to know how reliable is this code?

public void GenerateUniqueNumber(out string ValidUniqueNumber) {
        string GeneratedUniqueNumber = "";

        // Default implementation of UNIX time of the current UTC time
        TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
        string FormatedDateTime = Convert.ToInt64(ts.TotalSeconds).ToString();
        string ssUniqueId = DateTime.UtcNow.ToString("fffffff");
        //Add Padding to UniqueId
        string FormatedUniqueId = ssUniqueId.PadLeft(7, '0'); 

        if (FormatedDateTime.Length == 10 && FormatedUniqueId.Length == 7)
        {
            // Calculate checksum number using Luhn's algorithm.
            int sum = 0;
            bool odd = true;
            string InputData = FormatedDateTime + FormatedUniqueId;
            int CheckSumNumber;

            for (int i = InputData.Length - 1; i >= 0; i--)
            {
                if (odd == true)
                {
                    int tSum = Convert.ToInt32(InputData[i].ToString()) * 2;
                    if (tSum >= 10)
                    {
                        string tData = tSum.ToString();
                        tSum = Convert.ToInt32(tData[0].ToString()) + Convert.ToInt32(tData[1].ToString());
                    }
                    sum += tSum;
                }
                else
                    sum += Convert.ToInt32(InputData[i].ToString());
                odd = !odd;
            }
            //CheckSumNumber = (((sum / 10) + 1) * 10) - sum;
            CheckSumNumber = (((sum + 9) / 10) * 10) - sum;

            // Compute Full length 18 digit UniqueNumber
            GeneratedUniqueNumber = FormatedDateTime + FormatedUniqueId + Convert.ToString(CheckSumNumber);
        }
        else
        {
            // Error
            GeneratedUniqueNumber = Convert.ToString(-1);
        }

        ValidUniqueNumber = GeneratedUniqueNumber;        
    }

EDIT: clarification GUID can not be used, the number will need to be entered into a IVR system via telephone keypad.

+5  A: 

Why don't you just use a Guid?

Andrew Hare
cant use it, it contains letters, only want digits (numbers).
K001
A Guid is just a 128-bit number. The "letters" are just the hexadecimal representation of that number - easier to read it that way.
Mark H
But if his limit is 18 digits in decimal format, then a GUID will be too large, right?
CodingInsomnia
@andlju: Well, you could always convert it to decimal, then chop off anything past the first 18 digits. But that's pretty inelegant, and depending on the context, I hope there is a better way...
Matthew
No im not kidding, the number needs to be entered into a telephone, (cant enter GUID into a telephone) :(
K001
@Aaronaught: No need to mock him. If he already knew everything, he wouldn't need to ask a question. See the "Be nice" section in the FAQ: http://stackoverflow.com/faq
Matthew
@Matthew, thats right, i don't know everything, thats why i came here for suggestion/advice :)
K001
@Matthew: if you start chopping bits off a GUID, it is no longer globally unique. That's a really really bad idea.
jalf
@jalf: Sure, but isn't it only bad because you have fewer options to generate an id from (in this case, ~2^60 instead of 2^128)? And if so, how is this worse than any other "globally unique" 18-digit number? Or is there something I don't understand about how Guids work?Either way, it seems like generating a "globally unique" random number isn't the best solution in this case--he can just use an AUTO_INCREMENT field in his db.
Matthew
@Matthew, GUIDs are not random. They are globally unique because they contain a MAC address (unique, globally administered), and an accurate, high-resolution timestamp. Therefore, they are space-time coordinates, and it is impossible for them to collide, if we don't have MAC address collisions. See http://blogs.msdn.com/b/oldnewthing/archive/2008/06/27/8659071.aspx for an explanation
fahadsadah
@fahadsadah: Oh ok, that's really interesting. Thanks for clearing that up!
Matthew
@fahadsadah - GUIDs are not guaranteed to be unique; they simply have a good probability of being unique. At the end of the day, the only way to *guarantee* uniqueness is with something that maintains a list of the values generated (e.g. a database column with a unique constraint).
Thomas
@fahadsadah - One more item, I seem to remember reading somewhere the MS no longer uses the MAC to generate Guids since it provides a vector to identifying where the Guid was generated.
Thomas
@fahadsadah - From the Wikipedia article, V4 guids use a pseudo-random number instead of the MAC. http://en.wikipedia.org/wiki/Globally_Unique_Identifier
Thomas
@Thomas I was referring to V1, which uses real space-time coordinates. Also, how is a collision possible? If your MAC addresses aren't colliding, and you're not generating over 16,384 GUIDs in one clock tick (10ms), a collision should be impossible
fahadsadah
@fahadsadah - You mentioned the problem yourself. MAC address collisions, clock problems or simply passing the same guid twice because of a code mistake. "Impossible" imparts a pretty high level of protection. Is a collision so improbable that combined with a unique constraint you shouldn't worry about retries? Yes. But I'd still have a unique constraint on my data. Either way, if a guid is too fat, I think your idea of creating a custom unique identifier that is 18 digits is the right solution so +1.
Thomas
+3  A: 

If I understand your implementation correctly, it only uses the current date/time as a basis. That means that if you create two IDs simultaneously, they will not be unique.

CodingInsomnia
A: 

As "Andrew Hare" says, You can use Guid. About your code the answer is "NO"! because if client's computer's DateTime was wrong or change result may be couple or more!

Jalal Amini
A: 

No such thing as random anyway. Here's a suggestion.

  1. Create your own "random" 18 digit number
  2. Before sending it to the user, check it against existing ones in DB
  3. If already in DB, rinse and repeat.
IrishChieftain
Jeez, down votes with no comments, whatever next? ;-) Timestamps should not be used here and it's a given that you are coding with concurrency in mind. The DB check guarantees no duplicates; am I missing something? How you generate the number depends on the security needs of your situation. Your question stated that you want the number to be "unique forever" and this solves it :-)
IrishChieftain
Random and unique are not the same thing. Attempting to use *random* numbers to generate a *unique* identifier is an extremely bad idea. Even assuming you check against a database of previously-used numbers (and nowhere does the question suggest that such a database *exists* or is even *possible* in his environment), there's a theoretical risk of a non-halting algorithm and a very real risk of a major performance bottleneck. People need to stop using the word "random" in answers to questions about unique identifiers. If you've got a database, a *counter* would make a lot more sense.
Aaronaught
A DB was indicated by Khou in the comments section of his original question.
IrishChieftain
+6  A: 

You cannot use GUIDs, but you can create your own format of unique number similar to a GUID, that is based on the machine's MAC address (space) and the current time and date (time). This is guaranteed to be unique if the machines all have synchronised clocks.

For more information, please see here

fahadsadah
+1  A: 

Since you mentioned (in comments) that the IDs are stored in a DB, you can generate the IDs either using the method you mentioned or randomly and check for the existence in the DB.

If it already exists, generate a new one, otherwise you're done.

One thing though, I would make sure that checking for the existence of the ID and the actual saving of the record to the DB be done in a transaction, otherwise you run the risk of having another request create that record in between the checking for the ID and the creation of the row.

Also just checking, why wouldn't an auto-increment number generated by the database itself work? The DB would guarantee it's uniqueness (for that table anyway)

Davy8
+1  A: 

You don't say what the numbers are to be used for. Do they have some sort of value associated with them? Will it be a problem if users can figure out the scheme and guess valid ticket numbers?

If it is important for these numbers to be hard to guess, this scheme falls down; something that outputs data that looks really random would be better. You might take a monotonically increasing serial number and encrypt it using a block cipher (with a 64-bit block size); that gives you a 64-bit output or about 20 decimal digits worth, which you could take (say) the last 18 of. (If reversibility is important, i.e. given a ticket number you want to be able to recover the serial number, you need to be a bit more careful here.)

Do you need a cast-iron 100% guarantee that no ticket numbers will ever be the same? If so, you need to keep them in a database and mark them off when used. If you do that, it might be reasonable to just use a good random number generator and check for dupes every time.

crazyscot
+2  A: 

There are a few problems with this method:

  • You're basically just counting the number of milliseconds from January 1, 1970. You can get this from ts.TotalSeconds rounded to 0.0000001. All your conversion and millisecond calculation is unnecessary.

  • 10 years is about 3×10¹¹ milliseconds. You are keeping 17 significant digits, so for the next 10 years the first 5 digits will never change and cannot be used to distinguish numbers. They are useless.

  • Are you generating numbers for milliseconds between 1970 and now? If not, they also cannot be used to distinguish numbers and are useless.

  • This is totally dependent on what machine is returning the date. Anyone who has access to this machine can generate whatever "unique" numbers they want. Is this is problem?

  • Anyone who sees one of these numbers can tell when it was generated. Is this a problem?

  • Anyone can predict what number will be generated when. Is this a problem?

  • 1015 milliseconds is about 30000 years. After then, your algorithm will repeat numbers. Seems like a long time, but you specified "forever" and 30000 years is not "forever". Do you really mean "forever"?

Dour High Arch
I just noticed you require the number of digits in `TotalSeconds` to equal 10. This will break in 32 years, so your first 7 digits are useless. If you must truncate to 10 digits you should mod 10000000000.
Dour High Arch
This is totally dependent on what machine is returning the date. Anyone who has access to this machine can generate whatever "unique" numbers they want. Is this is problem? <-- not a problemAnyone who sees one of these numbers can tell when it was generated. Is this a problem? <-- not a problemAnyone can predict what number will be generated when. Is this a problem? <-- not a problem
K001
@Dour, how did you get 32 years?
K001
@Khou, by mistyping! It's actually 300 years: http://www.wolframalpha.com/input/?i=10000000000+seconds Still should use mod, though.
Dour High Arch
+1  A: 

Using the system time is a good start, but it gives you collisions if you need to generate two UIDs at the same time. It doesn't help that you're using the "fffffff" format: The Windows clock resolution is only 15-16 ms, so only one or two of those "f"s are doing any good.

Also, your approach tells you exactly when the ID was generated. Depending on your needs, this may be a desirable feature, or it may be a security risk.

You'll need your IDs to include other information instead of or in addition to the time. Some possible choices are:

  • A random number
  • A cyclic counter
  • A hash of the program name (if your need these IDs in multiple programs)
  • The MAC address or other identifier for the machine (If the IDs need to be unique across multiple computers)

If you want to ensure uniqueness, then store your IDs in a database so you can check for duplicates.

dan04