17

GUIDs (or Globally Unique Identifier) are usually stored as 128-bit values, and are commonly displayed as 32 hexadecimal digits with groups separated by hyphens, such as:

21EC2020-3AEA-4069-A2DD-08002B30309D

Online resources are available for creating pseudo-random GUIDs, but I'd like to create them during compile time. Ideally I'm interested in a macro \GUID that would produce a GUID (that I can store in a macro), perhaps using the computer time to initialise a pseudo-random number generator. If possible, perhaps an optional argument would allow one to supply a seed that should return a fixed GUID.

I'm specifically interested in Version 4 GUIDs which have the following format:

Hex digits  Description
8           Data1
4           Data2
4           Data3
4           Initial two bytes from Data4
12          Remaining six bytes from Data4

"Version 4 GUIDs simply use a pseudo-random number for filling in all but six of the bits. They have a 4 in the 4-bit version position, and the first two bits of data4 are 1 and 0 (so the first hex digit of data4 is 8, 9, A, or B), for example 38A52BE4-9352-453E-AF97-5C3B448652F0. More specifically, the data3 bit pattern would be [...] 0100xxxxxxxxxxxx [...]."

Werner
  • 603,163

5 Answers5

18

With random by D. Arsenau.

\documentclass{article}
\usepackage{xparse}
\input{random}

\ExplSyntaxOn \cs_set_eq:NN \guid_set_random_number:Nnn \setrannum \cs_set_eq:NN \g_guid_seed_int \randomi

\tl_new:N \l__guid_four_bytes_tl \int_new:N \l__guid_random_int

\cs_new_protected:Nn __guid_generate:n { \guid_set_random_number:Nnn \l__guid_random_int { 0 } { #1 } \int_case:nn { #1 } { { 4095 }{ \int_add:Nn \l__guid_random_int { "4000 } } { 16383 }{ \int_add:Nn \l__guid_random_int { 32768 } } } \tl_set:Nx \l__guid_four_bytes_tl { \int_to_Hex:n { \l__guid_random_int } } \tl_set:Nx \l__guid_four_bytes_tl { \prg_replicate:nn { 4 - \tl_count:N \l__guid_four_bytes_tl } { 0 } \tl_use:N \l__guid_four_bytes_tl } }

\NewDocumentCommand{\GUIDgenerate}{om} { \tl_new:c { g_guid_user_#2_tl } \group_begin: \IfValueT { #1 } { \int_gset:Nn \g_guid_seed_int { #1 } } % the seed % Data 1 (eight bytes) __guid_generate:n { 65535 } \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } __guid_generate:n { 65535 } \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } % hyphen \tl_gput_right:cx { g_guid_user_#2_tl } { - } % Data 2 (four bytes) __guid_generate:n { 65535 } \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } % hyphen \tl_gput_right:cx { g_guid_user_#2_tl } { - } % Data 3 (four bytes) __guid_generate:n { 4095 } \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } % hyphen \tl_gput_right:cx { g_guid_user_#2_tl } { - } % Data 4a (three bytes) __guid_generate:n { 16383 } \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } % hyphen \tl_gput_right:cx { g_guid_user_#2_tl } { - } % Data 4b (twelve bytes) __guid_generate:n { 65535 } \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } __guid_generate:n { 65535 } \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } __guid_generate:n { 65535 } \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } \group_end: }

\DeclareExpandableDocumentCommand{\GUID}{m} { \tl_use:c { g_guid_user_#1_tl } } \ExplSyntaxOff

\GUIDgenerate{fooA} \GUIDgenerate{fooB} \GUIDgenerate{fooC}

\begin{document}

\ttfamily \GUID{fooA}\par \GUID{fooB}\par \GUID{fooC}\par

\GUIDgenerate[2]{fooD} \GUIDgenerate[3]{fooE} \GUIDgenerate[42]{fooF}

\GUID{fooD}\par \GUID{fooE}\par \GUID{fooF}\par

\end{document}

If you compile this several times, you'll see that the first three GUIDs change, whereas the last three don't, because they're defined with a fixed seed.

enter image description here

UPDATE 2024

All engines now have a pseudorandom number generator.

\documentclass{article}

\ExplSyntaxOn % keep the initial (random) seed \int_const:Nn \c_guid_seed_int { \sys_rand_seed: }

\tl_new:N \l__guid_four_bytes_tl \int_new:N \l__guid_random_int

\cs_new_protected:Nn __guid_generate:n { \int_set:Nn \l__guid_random_int { \int_rand:nn { 0 } { #1 } } \int_case:nn { #1 } { { 4095 }{ \int_add:Nn \l__guid_random_int { "4000 } } { 16383 }{ \int_add:Nn \l__guid_random_int { 32768 } } } \tl_set:Ne \l__guid_four_bytes_tl { \int_to_Hex:n { \l__guid_random_int } } \tl_put_left:Ne \l__guid_four_bytes_tl { \prg_replicate:nn { 4 - \tl_count:N \l__guid_four_bytes_tl } { 0 } } }

\NewDocumentCommand{\GUIDgenerate}{om} { \tl_new:c { g_guid_user_#2_tl } % if with optional argument, use it a seed \IfValueT { #1 } { \sys_gset_rand_seed:n { #1 } } % the seed % Data 1 (eight bytes) __guid_generate:n { 65535 } \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } __guid_generate:n { 65535 } \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } % hyphen \tl_gput_right:ce { g_guid_user_#2_tl } { - } % Data 2 (four bytes) __guid_generate:n { 65535 } \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } % hyphen \tl_gput_right:ce { g_guid_user_#2_tl } { - } % Data 3 (four bytes) __guid_generate:n { 4095 } \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } % hyphen \tl_gput_right:ce { g_guid_user_#2_tl } { - } % Data 4a (three bytes) __guid_generate:n { 16383 } \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } % hyphen \tl_gput_right:ce { g_guid_user_#2_tl } { - } % Data 4b (twelve bytes) __guid_generate:n { 65535 } \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } __guid_generate:n { 65535 } \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } __guid_generate:n { 65535 } \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl } % reset the seed to the initial (random) value \IfValueT { #1 } { \sys_gset_rand_seed:n { \c_guid_seed_int } } % the seed }

\NewExpandableDocumentCommand{\GUID}{m} { \tl_use:c { g_guid_user_#1_tl } } \ExplSyntaxOff

\GUIDgenerate{fooA} \GUIDgenerate{fooB} \GUIDgenerate{fooC}

\begin{document}

\ttfamily \GUID{fooA}\par \GUID{fooB}\par \GUID{fooC}\par

\GUIDgenerate[2]{fooD} \GUIDgenerate[3]{fooE} \GUIDgenerate[42]{fooF}

\GUID{fooD}\par \GUID{fooE}\par \GUID{fooF}\par

\end{document}

egreg
  • 1,121,712
  • Of course one could use the random number generator provided by pdftex, but using random.tex the code above is portable across engines. – egreg Oct 03 '16 at 09:49
  • I don't get what you mean by "you'll see that the last three values don't change". Your own example seems to contradict this. Would you care to elaborate a little on that part? – user Oct 03 '16 at 11:13
  • 1
    @MichaelKjörling If you specify a seed, the random number generator will always spit out the same value. If you compile several times the example, the first three GUIDs will change every time, the last three will remain fixed. – egreg Oct 03 '16 at 11:58
  • Ah. That makes more sense. I thought you were referring to some part of the GUIDs themselves, not the GUIDs as a whole, hence my confusion. You may want to edit your answer to use the second sentence from your comment above. – user Oct 03 '16 at 12:18
  • the first hex digit of your data4 doesn't seem to be always 8, 9, A, or B. –  Oct 03 '16 at 14:34
  • @jfbu Thanks: binary numbers always bug me. – egreg Oct 03 '16 at 23:41
  • confirmed ;-) the first hex digit of your data3 isn't a 4 ;-) –  Oct 04 '16 at 06:50
  • @jfbu It's data4, not data3 – egreg Oct 04 '16 at 08:09
  • if I look at Werner's and David's examples they have a leading 4 in data3, as in the description. That is the 13th hex digit starting from the left must be 4. –  Oct 04 '16 at 08:35
  • look at https://en.wikipedia.org/wiki/Globally_unique_identifier#Algorithm and how this 4 is emphasized. I was similarly confused initially when reading Werner's post. –  Oct 04 '16 at 08:37
  • @jfbu I concentrated to the requirement about data4. The fix is easy enough. Thanks. – egreg Oct 04 '16 at 08:38
11

using the random generator from PDFTeX and package xintbinhex for hexadecimal conversions.

edit: I add a simpler no-package version. Still using pdfuniformdeviate.

I forgot to say that the uppercase letters are produced with catcode letter, they are the usual ones. (in case it matters at all...)

edit2: somehow I misread instructions and was producing only 28 hexadecimal digits. Fixed.

\documentclass{article}

\usepackage{xintbinhex}

\makeatletter
% there is a complication as we need to avoid stripping leading zeros...
% ... but I am going to use \xintDecToHex which does that.
%
\begingroup\catcode0 12
\gdef\@expand@and@gob {\expandafter\@gobble\romannumeral`^^@}
\endgroup

% this one is expandable
\newcommand*{\GUID}
{%
% Data 1: (8 Hex Digits)
% \pdfuniformdeviate will refuse higher than "7FFFFFFF
% \xintDecToHex wants tokens, hence \the\numexpr needed.
% generate five hex digits and gobble the first one (which will be 1)
 \@expand@and@gob
 \xintDecToHex {\the\numexpr "10000+\pdfuniformdeviate "FFFF\relax}% 
% again for a total of 8 Hex Digits
 \@expand@and@gob
 \xintDecToHex {\the\numexpr "10000+\pdfuniformdeviate "FFFF\relax}-% 
% Data 2: (4 Hex Digits)
 \@expand@and@gob
 \xintDecToHex {\the\numexpr "10000+\pdfuniformdeviate "FFFF\relax}-%
% Data 3: (4 Hex Digits, the first one a 4)
 \xintDecToHex {\the\numexpr "4000+\pdfuniformdeviate "FFF\relax}-%
% Data 4: (16=4+12 Hex Digits, the first one 8, 9 , A or B)
 \xintDecToHex {\the\numexpr  "8000+\pdfuniformdeviate "4000\relax}-%
 \@expand@and@gob
 \xintDecToHex {\the\numexpr "1000000+\pdfuniformdeviate "FFFFFF\relax}%
 \@expand@and@gob
 \xintDecToHex {\the\numexpr "1000000+\pdfuniformdeviate "FFFFFF\relax}%
 }   
 % side-note: if we used directly \pdfuniformdeviate in \xintDecToHex,
 % we could do it this way 
 % \xintDecToHex {\pdfuniformdeviate "FFFF }% <-- space needed
 % we don't do this above as \xintDecToHex trims leading zeros
\makeatother

% this one is not expandable
\newcommand*{\GUIDnx}[1][.]{\begingroup\ifx.#1\else
                          \pdfsetrandomseed #1\relax\fi \GUID\endgroup}

% this one is not expandable
\newcommand*{\GUIDset}[2][.]{\begingroup\ifx.#1\else
                          \pdfsetrandomseed #1\relax\fi \xdef#2{\GUID}\endgroup}

\begin{document}

\ttfamily

\GUID\par
\GUID\par
\GUID\par
\edef\foo{\GUID}\meaning\foo<-- end of macro\par
\edef\foo{\GUID}\meaning\foo<-- end of macro\par


\GUIDnx [0]\par

\GUIDset[0]\foo

\meaning\foo<-- end of macro\par

\GUIDnx [123456789]\par

\GUIDset[123456789]\foo

\meaning\foo<-- end of macro\par

\end{document}

%No package version

\documentclass{article}


\def\GUIDonedigit {\ifcase\pdfuniformdeviate 16\space\space
                    0\or 1\or 2\or 3%
                \or 4\or 5\or 6\or 7%
                \or 8\or 9\or A\or B%
                \or C\or D\or E\else F\fi}

\def\GUIDonespecialdigit {\ifcase\pdfuniformdeviate 4\space\space
                8\or 9\or A\else B\fi }

% this one is expandable


\newcommand*{\GUID}
{%
% Data 1: (8 Hex Digits)
\GUIDonedigit\GUIDonedigit\GUIDonedigit\GUIDonedigit
\GUIDonedigit\GUIDonedigit\GUIDonedigit\GUIDonedigit-%
% Data 2: (4 Hex Digits)
\GUIDonedigit\GUIDonedigit\GUIDonedigit\GUIDonedigit-%
% Data 3 : (4 Hex Digits, the first one a 4)
4\GUIDonedigit\GUIDonedigit\GUIDonedigit-%
% Data 4: (16=4+12 Hex Digits, the first one 8, 9 , A or B)
\GUIDonespecialdigit\GUIDonedigit\GUIDonedigit\GUIDonedigit-%
\GUIDonedigit\GUIDonedigit\GUIDonedigit\GUIDonedigit
\GUIDonedigit\GUIDonedigit\GUIDonedigit\GUIDonedigit
\GUIDonedigit\GUIDonedigit\GUIDonedigit\GUIDonedigit
 }


\makeatother

% this one is not expandable
\newcommand*{\GUIDnx}[1][.]{\begingroup\ifx.#1\else
                          \pdfsetrandomseed #1\relax\fi \GUID\endgroup}

% this one is not expandable
\newcommand*{\GUIDset}[2][.]{\begingroup\ifx.#1\else
                          \pdfsetrandomseed #1\relax\fi \xdef#2{\GUID}\endgroup}

\begin{document}

\ttfamily

\GUID\par
\GUID\par
\GUID\par
\edef\foo{\GUID}\meaning\foo<-- end of macro\par
\edef\foo{\GUID}\meaning\foo<-- end of macro\par


\GUIDnx [0]\par

\GUIDset[0]\foo

\meaning\foo<-- end of macro\par

\GUIDnx [123456789]\par

\GUIDset[123456789]\foo

\meaning\foo<-- end of macro\par

\end{document}

enter image description here

6

this might not work in all tex engines.

enter image description here

{\catcode`\%=12
\gdef\GUID{\directlua{
tex.print(string.format("%08x-%04x-%04x-%04x-%012x",
math.random(0xffffffff),
math.random(0xffff),
0x4000+math.random(0xfff),
0x8000+math.random(0x3fff),
math.random(0xffffffffffff)
))
}}}

\tt

\edef\foo{\GUID}\foo

\GUID


\bye
David Carlisle
  • 757,742
3

One more way of using PDFTeX random generator primitives.

Macro \GUID is used to generate random GUID, macro \newGUID#1 generates a random GUID and binds it with provided ID, macro \getGUID#1 returns the GUID, binded with provided ID.

Plain pdftex MWE

\def\decdig#1{\expandafter\def\csname#1@@@\endcsname{#1}}
\def\hexdig#1#2{\expandafter\def\csname#1@@@\endcsname{#2}}

\decdig0\decdig1\decdig2\decdig3\decdig4\decdig5\decdig6\decdig7\decdig8\decdig9
\hexdig{10}A\hexdig{11}B\hexdig{12}C\hexdig{13}D\hexdig{14}E\hexdig{15}F

\def\hexdigit#1{\csname#1@@@\endcsname}
\def\X{\hexdigit{\pdfuniformdeviate16}}
\def\XX{\X\X}
\def\XXXX{\XX\XX}

\def\GUID{
    \XXXX\XXXX-\XXXX-4\XX\X-%
    \hexdigit{\the\numexpr8+\pdfuniformdeviate4\relax}\XX\X-\XXXX\XXXX\XXXX}

\def\newGUID#1{\expandafter\edef\csname#1@@@@@\endcsname{\GUID}}
\def\getGUID#1{\csname#1@@@@@\endcsname}

\pdfsetrandomseed1371

Random GUIDs

{
    \tt
    \GUID

    \GUID

    \GUID

    \GUID

    \GUID
}
%
\newGUID{1} \newGUID{1212} \newGUID{special}

\ 

\settabs\+ special\quad&\tt\getGUID{1}&\cr 
\+ Saved GUIDs \cr
\+ ID      & \hfill GUID \hfill  &\cr
\+ 1       & \tt\getGUID{1}      \cr
\+ 1212    & \tt\getGUID{1212}   \cr
\+ special & \tt\getGUID{special}\cr
\+ 1212    & \tt\getGUID{1212}   \cr
\+ 1       & \tt\getGUID{1}      \cr
\+ special & \tt\getGUID{special}\cr


\bye

enter image description here

pdflatex MWE, (essentially the same)

\documentclass{article}

\def\decdig#1{\expandafter\def\csname#1@@@\endcsname{#1}}
\def\hexdig#1#2{\expandafter\def\csname#1@@@\endcsname{#2}}

\decdig0\decdig1\decdig2\decdig3\decdig4\decdig5\decdig6\decdig7\decdig8\decdig9
\hexdig{10}A\hexdig{11}B\hexdig{12}C\hexdig{13}D\hexdig{14}E\hexdig{15}F

\def\hexdigit#1{\csname#1@@@\endcsname}
\def\X{\hexdigit{\pdfuniformdeviate16}}
\def\XX{\X\X}
\def\XXXX{\XX\XX}

\def\GUID{
    \XXXX\XXXX-\XXXX-4\XX\X-%
    \hexdigit{\the\numexpr8+\pdfuniformdeviate4\relax}\XX\X-\XXXX\XXXX\XXXX}

\def\newGUID#1{\expandafter\edef\csname#1@@@@@\endcsname{\GUID}}
\def\getGUID#1{\csname#1@@@@@\endcsname}


\begin{document}

    \pdfsetrandomseed1371

    Random GUIDs

    \texttt{\GUID}

    \texttt{\GUID}

    \texttt{\GUID}

    \texttt{\GUID}

    \texttt{\GUID}

    \ 

    \newGUID{1} \newGUID{1212} \newGUID{special}

    Saved GUIDs 

    \begin{tabular}{cc}
        ID & GUID\\
        1&\texttt{\getGUID{1}} \\
        1212&\texttt{\getGUID{1212}} \\
        special & \texttt{\getGUID{special}}\\
        1212&\texttt{\getGUID{1212}} \\
        1&\texttt{\getGUID{1}} \\
        special & \texttt{\getGUID{special}}
    \end{tabular}


\end{document}

enter image description here

g.kov
  • 21,864
  • 1
  • 58
  • 95
1

Yet more! This one uses md5sum, so it is not technically "random" in the sense of uuid v4, but nobody will notice.

This code provides a specific documentID that does not change, unless you do something to change it. The instanceID is based on time, thus always changes.

Caution: If you are not willing to change "something" in the documentID generator, as needed, then do not use this.

\documentclass{article}
% My own usage already has these packages loaded for other reasons,
% so I use them here. No doubt the code can be re-written so that
% the mdfivesum and string manipulations can be done directly.
\RequirePackage{pdftexcmds}
\RequirePackage{xstring}
% Farther down, \tempstr will be defined. It begins as an mdfivesum with
% uppercase hex, no hyphens, and no particular hexcodes in two locations
% where uuid version 4 expects particular hexcodes. This tweak puts the
% letters in lowercase (via egreg code elsewhere), then adds the hyphens and
% specific hexcodes:
\def\tweaktempstr{
  \lowercase\expandafter{% Per 'egreg' tex.stackexchange.com q.351065.
    \expandafter\def\expandafter\tempstr\expandafter{\tempstr}%
  }
  \StrLeft{\tempstr}{8}[\tempn]
  \StrRight{\tempstr}{24}[\tempd]
  \edef\tempstr{\tempn-\tempd}
  \StrLeft{\tempstr}{13}[\tempn]
  \StrRight{\tempstr}{19}[\tempd] % Omit character, becomes 4.
  \edef\tempstr{\tempn-4\tempd}
  \StrLeft{\tempstr}{18}[\tempn]
  \StrRight{\tempstr}{15}[\tempd] % Omit character, becomes 8.
  \edef\tempstr{\tempn-8\tempd}
  \StrLeft{\tempstr}{23}[\tempn]
  \StrRight{\tempstr}{12}[\tempd]
  \edef\tempstr{\tempn-\tempd}
}
% Change "somthing" so that documents with same jobname are distinguished:
\makeatletter
\edef\tempstr{\pdf@mdfivesum{\jobname something}}
\tweaktempstr
\edef\documentID{uuid:\tempstr}
\ifdefined\pdffeedback % lualatex
  \edef\tempstr{\pdf@mdfivesum{\pdffeedback creationdate}}
\else % pdflatex
  \edef\tempstr{\pdf@mdfivesum{\pdfcreationdate}}
\fi
\tweaktempstr
\edef\instanceID{uuid:\tempstr}
\makeatother
%%
\begin{document}
\ttfamily % Only so that strings are easier to see:
documentID = \documentID\par
instanceID = \instanceID\par
\end{document}

screenshot of result

rallg
  • 2,379