private static unsafe string FormatGuidAsString(Guid guid)
{
// the conversion is based on the pen-and-paper algorithm for converting between bases:
// 1. divide input by base, remainder is least significant output digit.
// 2. divide quotient by base, remainder is next least significant output digit.
// 3. repeat until quotient is zero.
//
// however, we are unable to write normal arirthmetic operations here because the operands are 128-bits, and we can do at best 64-bits!
// the solution is to do "long division", i.e. division in smaller, manageable units, and carry over the remainder to lower places
// we choose to break the number into 32-bits at a time, so that we can use 64-bit arirthmetic to accomodate each part plus the carry over
// put another way, we are "rewriting" the 128-bit number as a 4-digit base 2^32 number
//
// thus, our long division algorithm now looks like this:
// A. divide the most significant word by base, quotient is most significant word of overall quotient
// B. divide the number (remainder * word base + next most significant word) by base, quotient is next most significant word of overall quotient
// C. repeat until least significant word, where the remainder is simply the overall remainder
//
// the resulting algorithm is perhaps more convoluted than if we were to divide one byte at a time, but this does make it more efficient since
// we are leveraging the CPU's native 32/64-bit binary base for arithmetic
//
// NOTE: The simpler algorithm for converting between bases is to start from the most significant digit, multiply by base, add next most
// significant digit, multiply all that by base, and so on until you finish the sequence of digits. This does not work for us, because the
// result of each stage is exponentially increasing, and you will quickly exceed the capabilities of native CPU arithmetic.
// convert the 128-bits of the GUID to 4x 32-bit unsigned ints
var bytes = GuidToSystemEndianBytes(guid);
// allocate space for the string - we know it's at most 39 decimal digits
const int maxDigits = 39;
var chars = new char[maxDigits];
fixed (char* pChars = chars)
fixed (byte* pBytes = bytes)
{
// algorithm produces least significant digit first, so we set digits from the end of the string, and keep track of how many digits
var countDigits = 0;
var pC = &pChars[maxDigits - 1];
// casts the bytes to a uint pointer, since we've rearranged the GUID as such
var pB = (uint*) pBytes;
do
{
// take the first word, divide by 10, and keep the quotient for the next round of calculations
ulong r = pB[0];
ulong q = r/10;
pB[0] = (uint) q;
// the remainder (i.e. r - q*10) is prepended to the second word as the most significant digit
// and then divide by 10, and keep the quotient for the next round of calculations
r = ((r - q*10) << 32) + pB[1];
q = r/10;
pB[1] = (uint) q;
// the remainder is prepended to the third word as the most significant digit
// and then divide by 10, and keep the quotient for the next round of calculations
r = ((r - q*10) << 32) + pB[2];
q = r/10;
pB[2] = (uint) q;
// the remainder is prepended to the fourth word as the most significant digit
// and then divide by 10, and keep the quotient for the next round of calculations
r = ((r - q*10) << 32) + pB[3];
q = r/10;
pB[3] = (uint) q;
// the remainder is the next decimal digit in the result
r = r - q*10;
// the digits are yielded from least to most significant, so we fill the char array from the end
*pC-- = (char) ('0' + r); // '0'+r being a way of converting a number between 0 and 9 to the equivalent character '0' to '9'
// and keep track of how many digits that is
++countDigits;
// when the dividend for the next round of calculations is 0 (i.e. all words are 0), we are done
} while (pB[0] != 0 || pB[1] != 0 || pB[2] != 0 || pB[3] != 0);
// now return a string based on the pointer and offset based on number of digits we actually produced
// note that the loop always produces at least one digit, even if that is '0'
return new string(pChars, maxDigits - countDigits, countDigits);
}
}