using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; #if UNITY_DOTSRUNTIME using Unity.Collections.LowLevel.Unsafe; #endif namespace Unity.Burst { #if BURST_COMPILER_SHARED internal static partial class BurstStringInternal #else internal static partial class BurstString #endif { // This file provides an implementation for formatting floating point numbers that is compatible // with Burst // ------------------------------------------------------------------------------ // Part of code translated to C# from http://www.ryanjuckett.com/programming/printing-floating-point-numbers // with the following license: /****************************************************************************** Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ******************************************************************************/ //****************************************************************************** // Get the log base 2 of a 32-bit unsigned integer. // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup //****************************************************************************** private static readonly byte[] logTable = new byte[256] { 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 }; private static uint LogBase2(uint val) { uint temp; temp = val >> 24; if (temp != 0) return (uint)(24 + logTable[(int)temp]); temp = val >> 16; if (temp != 0) return (uint)(16 + logTable[temp]); temp = val >> 8; if (temp != 0) return (uint)(8 + logTable[temp]); return logTable[val]; } //****************************************************************************** // This structure stores a high precision unsigned integer. It uses a buffer // of 32 bit integer blocks along with a length. The lowest bits of the integer // are stored at the start of the buffer and the length is set to the minimum // value that contains the integer. Thus, there are never any zero blocks at the // end of the buffer. //****************************************************************************** public unsafe struct tBigInt { //****************************************************************************** // Maximum number of 32 bit blocks needed in high precision arithmetic // to print out 64 bit IEEE floating point values. //****************************************************************************** const int c_BigInt_MaxBlocks = 35; //// Copy integer //tBigInt & operator=(tBigInt &rhs) //{ // uint length = rhs.m_length; // uint* pLhsCur = m_blocks; // for (uint* pRhsCur = rhs.m_blocks, *pRhsEnd = pRhsCur + length; // pRhsCur != pRhsEnd; // ++pLhsCur, ++pRhsCur) // { // *pLhsCur = *pRhsCur; // } // m_length = length; // return *this; //} // Data accessors public int GetLength() { return m_length; } public uint GetBlock(int idx) { return m_blocks[idx]; } // Zero helper functions public void SetZero() { m_length = 0; } public bool IsZero() { return m_length == 0; } // Basic type accessors public void SetU64(ulong val) { if (val > 0xFFFFFFFF) { m_blocks[0] = (uint)(val & 0xFFFFFFFF); m_blocks[1] = (uint)(val >> 32 & 0xFFFFFFFF); m_length = 2; } else if (val != 0) { m_blocks[0] = (uint)(val & 0xFFFFFFFF); m_length = 1; } else { m_length = 0; } } public void SetU32(uint val) { if (val != 0) { m_blocks[0] = val; m_length = (val != 0) ? 1 : 0; } else { m_length = 0; } } public uint GetU32() { return (m_length == 0) ? 0 : m_blocks[0]; } // Member data public int m_length; public fixed uint m_blocks[c_BigInt_MaxBlocks]; } //****************************************************************************** // Returns 0 if (lhs = rhs), negative if (lhs < rhs), positive if (lhs > rhs) //****************************************************************************** private static unsafe int BigInt_Compare(in tBigInt lhs, in tBigInt rhs) { // A bigger length implies a bigger number. int lengthDiff = lhs.m_length - rhs.m_length; if (lengthDiff != 0) return lengthDiff; // Compare blocks one by one from high to low. for (int i = (int)lhs.m_length - 1; i >= 0; --i) { if (lhs.m_blocks[i] == rhs.m_blocks[i]) continue; else if (lhs.m_blocks[i] > rhs.m_blocks[i]) return 1; else return -1; } // no blocks differed return 0; } //****************************************************************************** // result = lhs + rhs //****************************************************************************** private static unsafe void BigInt_Add(out tBigInt pResult, in tBigInt lhs, in tBigInt rhs) { if (lhs.m_length < rhs.m_length) { BigInt_Add_internal(out pResult, rhs, lhs); } else { BigInt_Add_internal(out pResult, lhs, rhs); } } private static unsafe void BigInt_Add_internal(out tBigInt pResult, in tBigInt pLarge, in tBigInt pSmall) { int largeLen = pLarge.m_length; int smallLen = pSmall.m_length; // The output will be at least as long as the largest input pResult.m_length = largeLen; // Add each block and add carry the overflow to the next block ulong carry = 0; fixed (uint * pLargeCur1 = pLarge.m_blocks) fixed (uint * pSmallCur1 = pSmall.m_blocks) fixed (uint * pResultCur1 = pResult.m_blocks) { uint* pLargeCur = pLargeCur1; uint* pSmallCur = pSmallCur1; uint* pResultCur = pResultCur1; uint* pLargeEnd = pLargeCur + largeLen; uint* pSmallEnd = pSmallCur + smallLen; while (pSmallCur != pSmallEnd) { ulong sum = carry + (ulong) (*pLargeCur) + (ulong) (*pSmallCur); carry = sum >> 32; (*pResultCur) = (uint)(sum & 0xFFFFFFFF); ++pLargeCur; ++pSmallCur; ++pResultCur; } // Add the carry to any blocks that only exist in the large operand while (pLargeCur != pLargeEnd) { ulong sum = carry + (ulong) (*pLargeCur); carry = sum >> 32; (*pResultCur) = (uint)(sum & 0xFFFFFFFF); ++pLargeCur; ++pResultCur; } // If there's still a carry, append a new block if (carry != 0) { //RJ_ASSERT(carry == 1); //RJ_ASSERT((uint)(pResultCur - pResult.m_blocks) == largeLen && (largeLen < c_BigInt_MaxBlocks)); *pResultCur = 1; pResult.m_length = largeLen + 1; } else { pResult.m_length = largeLen; } } } //****************************************************************************** // result = lhs * rhs //****************************************************************************** private static unsafe void BigInt_Multiply(out tBigInt pResult, in tBigInt lhs, in tBigInt rhs) { if (lhs.m_length < rhs.m_length) { BigInt_Multiply_internal(out pResult, rhs, lhs); } else { BigInt_Multiply_internal(out pResult, lhs, rhs); } } private static unsafe void BigInt_Multiply_internal(out tBigInt pResult, in tBigInt pLarge, in tBigInt pSmall) { // set the maximum possible result length int maxResultLen = pLarge.m_length + pSmall.m_length; // RJ_ASSERT( maxResultLen <= c_BigInt_MaxBlocks ); // clear the result data // uint * pCur = pResult.m_blocks, *pEnd = pCur + maxResultLen; pCur != pEnd; ++pCur for (int i = 0; i < maxResultLen; i++) pResult.m_blocks[i] = 0; // perform standard long multiplication fixed (uint *pLargeBeg1 = pLarge.m_blocks) { uint* pLargeBeg = pLargeBeg1; uint* pLargeEnd = pLargeBeg + pLarge.m_length; // for each small block fixed (uint* pResultStart1 = pResult.m_blocks) fixed (uint* pSmallCur1 = pSmall.m_blocks) { uint* pSmallCur = pSmallCur1; uint* pSmallEnd = pSmallCur + pSmall.m_length; uint* pResultStart = pResultStart1; for (; pSmallCur != pSmallEnd; ++pSmallCur, ++pResultStart) { // if non-zero, multiply against all the large blocks and add into the result uint multiplier = *pSmallCur; if (multiplier != 0) { uint* pLargeCur = pLargeBeg; uint* pResultCur = pResultStart; ulong carry = 0; do { ulong product = (*pResultCur) + (*pLargeCur) * (ulong) multiplier + carry; carry = product >> 32; *pResultCur = (uint)(product & 0xFFFFFFFF); ++pLargeCur; ++pResultCur; } while (pLargeCur != pLargeEnd); //RJ_ASSERT(pResultCur < pResult.m_blocks + maxResultLen); *pResultCur = (uint) (carry & 0xFFFFFFFF); } } // check if the terminating block has no set bits if (maxResultLen > 0 && pResult.m_blocks[maxResultLen - 1] == 0) pResult.m_length = maxResultLen - 1; else pResult.m_length = maxResultLen; } } } //****************************************************************************** // result = lhs * rhs //****************************************************************************** private static unsafe void BigInt_Multiply(out tBigInt pResult, in tBigInt lhs, uint rhs) { // perform long multiplication uint carry = 0; fixed (uint* pResultCur1 = pResult.m_blocks) fixed (uint* pLhsCur1 = lhs.m_blocks) { uint* pResultCur = pResultCur1; uint* pLhsCur = pLhsCur1; uint* pLhsEnd = pLhsCur + lhs.m_length; for (; pLhsCur != pLhsEnd; ++pLhsCur, ++pResultCur) { ulong product = (ulong) (*pLhsCur) * rhs + carry; *pResultCur = (uint) (product & 0xFFFFFFFF); carry = (uint)(product >> 32); } // if there is a remaining carry, grow the array if (carry != 0) { // grow the array //RJ_ASSERT(lhs.m_length + 1 <= c_BigInt_MaxBlocks); *pResultCur = (uint) carry; pResult.m_length = lhs.m_length + 1; } else { pResult.m_length = lhs.m_length; } } } //****************************************************************************** // result = in * 2 //****************************************************************************** private static unsafe void BigInt_Multiply2(out tBigInt pResult, in tBigInt input) { // shift all the blocks by one uint carry = 0; fixed (uint* pResultCur1 = pResult.m_blocks) fixed (uint* pLhsCur1 = input.m_blocks) { uint* pResultCur = pResultCur1; uint* pLhsCur = pLhsCur1; uint* pLhsEnd = pLhsCur + input.m_length; for (; pLhsCur != pLhsEnd; ++pLhsCur, ++pResultCur) { uint cur = *pLhsCur; *pResultCur = (cur << 1) | carry; carry = cur >> 31; } if (carry != 0) { // grow the array // RJ_ASSERT(input.m_length + 1 <= c_BigInt_MaxBlocks); *pResultCur = carry; pResult.m_length = input.m_length + 1; } else { pResult.m_length = input.m_length; } } } //****************************************************************************** // result = result * 2 //****************************************************************************** private static unsafe void BigInt_Multiply2(ref tBigInt pResult) { // shift all the blocks by one uint carry = 0; fixed (uint* pCur1 = pResult.m_blocks) { uint* pCur = pCur1; uint* pEnd = pCur + pResult.m_length; for (; pCur != pEnd; ++pCur) { uint cur = *pCur; *pCur = (cur << 1) | carry; carry = cur >> 31; } if (carry != 0) { // grow the array // RJ_ASSERT(pResult.m_length + 1 <= c_BigInt_MaxBlocks); *pCur = carry; ++pResult.m_length; } } } //****************************************************************************** // result = result * 10 //****************************************************************************** private static unsafe void BigInt_Multiply10(ref tBigInt pResult) { // multiply all the blocks ulong carry = 0; fixed (uint* pCur1 = pResult.m_blocks) { uint* pCur = pCur1; uint* pEnd = pCur + pResult.m_length; for (; pCur != pEnd; ++pCur) { ulong product = (ulong) (*pCur) * 10 + carry; (*pCur) = (uint) (product & 0xFFFFFFFF); carry = product >> 32; } if (carry != 0) { // grow the array //RJ_ASSERT(pResult.m_length + 1 <= c_BigInt_MaxBlocks); *pCur = (uint) carry; ++pResult.m_length; } } } //****************************************************************************** //****************************************************************************** private static readonly uint[] g_PowerOf10_U32 = new uint[] { 1, // 10 ^ 0 10, // 10 ^ 1 100, // 10 ^ 2 1000, // 10 ^ 3 10000, // 10 ^ 4 100000, // 10 ^ 5 1000000, // 10 ^ 6 10000000, // 10 ^ 7 }; //****************************************************************************** // Note: This has a lot of wasted space in the big integer structures of the // early table entries. It wouldn't be terribly hard to make the multiply // function work on integer pointers with an array length instead of // the tBigInt struct which would allow us to store a minimal amount of // data here. //****************************************************************************** private static unsafe tBigInt g_PowerOf10_Big(int i) { tBigInt result; // 10 ^ 8 if (i == 0) { // { 1, { 100000000 } }, result.m_length = 1; result.m_blocks[0] = 100000000; } else if (i == 1) { // 10 ^ 16 // { 2, { 0x6fc10000, 0x002386f2 } }, result.m_length = 2; result.m_blocks[0] = 0x6fc10000; result.m_blocks[1] = 0x002386f2; } else if (i == 2) { // 10 ^ 32 // { 4, { 0x00000000, 0x85acef81, 0x2d6d415b, 0x000004ee, } }, result.m_length = 4; result.m_blocks[0] = 0x00000000; result.m_blocks[1] = 0x85acef81; result.m_blocks[2] = 0x2d6d415b; result.m_blocks[3] = 0x000004ee; } else if (i == 3) { // 10 ^ 64 // { 7, { 0x00000000, 0x00000000, 0xbf6a1f01, 0x6e38ed64, 0xdaa797ed, 0xe93ff9f4, 0x00184f03, } }, result.m_length = 7; result.m_blocks[0] = 0x00000000; result.m_blocks[1] = 0x00000000; result.m_blocks[2] = 0xbf6a1f01; result.m_blocks[3] = 0x6e38ed64; result.m_blocks[4] = 0xdaa797ed; result.m_blocks[5] = 0xe93ff9f4; result.m_blocks[6] = 0x00184f03; } else if (i == 4) { // 10 ^ 128 //{ // 14, { // 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x2e953e01, 0x03df9909, 0x0f1538fd, // 0x2374e42f, 0xd3cff5ec, 0xc404dc08, 0xbccdb0da, 0xa6337f19, 0xe91f2603, 0x0000024e, } //}, result.m_length = 14; result.m_blocks[0] = 0x00000000; result.m_blocks[1] = 0x00000000; result.m_blocks[2] = 0x00000000; result.m_blocks[3] = 0x00000000; result.m_blocks[4] = 0x2e953e01; result.m_blocks[5] = 0x03df9909; result.m_blocks[6] = 0x0f1538fd; result.m_blocks[7] = 0x2374e42f; result.m_blocks[8] = 0xd3cff5ec; result.m_blocks[9] = 0xc404dc08; result.m_blocks[10] = 0xbccdb0da; result.m_blocks[11] = 0xa6337f19; result.m_blocks[12] = 0xe91f2603; result.m_blocks[13] = 0x0000024e; } else { // 10 ^ 256 //{ // 27, { // 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // 0x00000000, 0x982e7c01, 0xbed3875b, 0xd8d99f72, 0x12152f87, 0x6bde50c6, 0xcf4a6e70, // 0xd595d80f, 0x26b2716e, 0xadc666b0, 0x1d153624, 0x3c42d35a, 0x63ff540e, 0xcc5573c0, // 0x65f9ef17, 0x55bc28f2, 0x80dcc7f7, 0xf46eeddc, 0x5fdcefce, 0x000553f7, // } //} result.m_length = 27; result.m_blocks[0] = 0x00000000; result.m_blocks[1] = 0x00000000; result.m_blocks[2] = 0x00000000; result.m_blocks[3] = 0x00000000; result.m_blocks[4] = 0x00000000; result.m_blocks[5] = 0x00000000; result.m_blocks[6] = 0x00000000; result.m_blocks[7] = 0x00000000; result.m_blocks[8] = 0x982e7c01; result.m_blocks[9] = 0xbed3875b; result.m_blocks[10] = 0xd8d99f72; result.m_blocks[11] = 0x12152f87; result.m_blocks[12] = 0x6bde50c6; result.m_blocks[13] = 0xcf4a6e70; result.m_blocks[14] = 0xd595d80f; result.m_blocks[15] = 0x26b2716e; result.m_blocks[16] = 0xadc666b0; result.m_blocks[17] = 0x1d153624; result.m_blocks[18] = 0x3c42d35a; result.m_blocks[19] = 0x63ff540e; result.m_blocks[20] = 0xcc5573c0; result.m_blocks[21] = 0x65f9ef17; result.m_blocks[22] = 0x55bc28f2; result.m_blocks[23] = 0x80dcc7f7; result.m_blocks[24] = 0xf46eeddc; result.m_blocks[25] = 0x5fdcefce; result.m_blocks[26] = 0x000553f7; } return result; } //****************************************************************************** // result = 10^exponent //****************************************************************************** private static void BigInt_Pow10(out tBigInt pResult, uint exponent) { // make sure the exponent is within the bounds of the lookup table data // RJ_ASSERT(exponent < 512); // create two temporary values to reduce large integer copy operations tBigInt temp1 = default; tBigInt temp2 = default; ref tBigInt pCurTemp = ref temp1; ref tBigInt pNextTemp = ref temp2; // initialize the result by looking up a 32-bit power of 10 corresponding to the first 3 bits uint smallExponent = exponent & 0x7; pCurTemp.SetU32(g_PowerOf10_U32[smallExponent]); // remove the low bits that we used for the 32-bit lookup table exponent >>= 3; int tableIdx = 0; // while there are remaining bits in the exponent to be processed while (exponent != 0) { // if the current bit is set, multiply it with the corresponding power of 10 if ((exponent & 1) != 0) { // multiply into the next temporary BigInt_Multiply(out pNextTemp, pCurTemp, g_PowerOf10_Big(tableIdx)); // swap to the next temporary ref tBigInt pSwap = ref pCurTemp; pCurTemp = pNextTemp; pNextTemp = pSwap; } // advance to the next bit ++tableIdx; exponent >>= 1; } // output the result pResult = pCurTemp; } //****************************************************************************** // result = in * 10^exponent //****************************************************************************** private static unsafe void BigInt_MultiplyPow10(out tBigInt pResult, in tBigInt input, uint exponent) { // make sure the exponent is within the bounds of the lookup table data // RJ_ASSERT(exponent < 512); // create two temporary values to reduce large integer copy operations tBigInt temp1 = default; tBigInt temp2 = default; ref tBigInt pCurTemp = ref temp1; ref tBigInt pNextTemp = ref temp2; // initialize the result by looking up a 32-bit power of 10 corresponding to the first 3 bits uint smallExponent = exponent & 0x7; if (smallExponent != 0) { BigInt_Multiply(out pCurTemp, input, g_PowerOf10_U32[smallExponent]); } else { pCurTemp = input; } // remove the low bits that we used for the 32-bit lookup table exponent >>= 3; int tableIdx = 0; // while there are remaining bits in the exponent to be processed while (exponent != 0) { // if the current bit is set, multiply it with the corresponding power of 10 if((exponent & 1) != 0) { // multiply into the next temporary BigInt_Multiply( out pNextTemp, pCurTemp, g_PowerOf10_Big(tableIdx) ); // swap to the next temporary ref tBigInt pSwap = ref pCurTemp; pCurTemp = pNextTemp; pNextTemp = pSwap; } // advance to the next bit ++tableIdx; exponent >>= 1; } // output the result pResult = pCurTemp; } //****************************************************************************** // result = 2^exponent //****************************************************************************** private static unsafe void BigInt_Pow2(out tBigInt pResult, uint exponent) { int blockIdx = (int)exponent / 32; //RJ_ASSERT(blockIdx < c_BigInt_MaxBlocks); for (uint i = 0; i <= blockIdx; ++i) pResult.m_blocks[i] = 0; pResult.m_length = blockIdx + 1; int bitIdx = ((int)exponent % 32); pResult.m_blocks[blockIdx] |= (uint)(1 << bitIdx); } //****************************************************************************** // This function will divide two large numbers under the assumption that the // result is within the range [0,10) and the input numbers have been shifted // to satisfy: // - The highest block of the divisor is greater than or equal to 8 such that // there is enough precision to make an accurate first guess at the quotient. // - The highest block of the divisor is less than the maximum value on an // unsigned 32-bit integer such that we can safely increment without overflow. // - The dividend does not contain more blocks than the divisor such that we // can estimate the quotient by dividing the equivalently placed high blocks. // // quotient = floor(dividend / divisor) // remainder = dividend - quotient*divisor // // pDividend is updated to be the remainder and the quotient is returned. //****************************************************************************** private static unsafe uint BigInt_DivideWithRemainder_MaxQuotient9(ref tBigInt pDividend, in tBigInt divisor) { // Check that the divisor has been correctly shifted into range and that it is not // smaller than the dividend in length. //RJ_ASSERT( !divisor.IsZero() && // divisor.m_blocks[divisor.m_length-1] >= 8 && // divisor.m_blocks[divisor.m_length-1] < 0xFFFFFFFF && // pDividend->m_length <= divisor.m_length ); // If the dividend is smaller than the divisor, the quotient is zero and the divisor is already // the remainder. int length = divisor.m_length; if (pDividend.m_length < divisor.m_length) return 0; fixed (uint* pDivisorCur1 = divisor.m_blocks) fixed (uint* pDividendCur1 = pDividend.m_blocks) { uint* pDivisorCur = pDivisorCur1; uint* pDividendCur = pDividendCur1; uint* pFinalDivisorBlock = pDivisorCur + length - 1; uint* pFinalDividendBlock = pDividendCur + length - 1; // Compute an estimated quotient based on the high block value. This will either match the actual quotient or // undershoot by one. uint quotient = *pFinalDividendBlock / (*pFinalDivisorBlock + 1); //RJ_ASSERT(quotient <= 9); // Divide out the estimated quotient if (quotient != 0) { // dividend = dividend - divisor*quotient ulong borrow = 0; ulong carry = 0; do { ulong product = (ulong) *pDivisorCur * (ulong) quotient + carry; carry = product >> 32; ulong difference = (ulong) *pDividendCur - (product & 0xFFFFFFFF) - borrow; borrow = (difference >> 32) & 1; *pDividendCur = (uint) (difference & 0xFFFFFFFF); ++pDivisorCur; ++pDividendCur; } while (pDivisorCur <= pFinalDivisorBlock); // remove all leading zero blocks from dividend while (length > 0 && pDividend.m_blocks[length - 1] == 0) --length; pDividend.m_length = length; } // If the dividend is still larger than the divisor, we overshot our estimate quotient. To correct, // we increment the quotient and subtract one more divisor from the dividend. if (BigInt_Compare(pDividend, divisor) >= 0) { ++quotient; // dividend = dividend - divisor pDivisorCur = pDivisorCur1; pDividendCur = pDividendCur1; ulong borrow = 0; do { ulong difference = (ulong) *pDividendCur - (ulong) *pDivisorCur - borrow; borrow = (difference >> 32) & 1; *pDividendCur = (uint)(difference & 0xFFFFFFFF); ++pDivisorCur; ++pDividendCur; } while (pDivisorCur <= pFinalDivisorBlock); // remove all leading zero blocks from dividend while (length > 0 && pDividend.m_blocks[length - 1] == 0) --length; pDividend.m_length = length; } return quotient; } } //****************************************************************************** // result = result << shift //****************************************************************************** private static unsafe void BigInt_ShiftLeft(ref tBigInt pResult, uint shift) { // RJ_ASSERT( shift != 0 ); int shiftBlocks = (int)shift / 32; int shiftBits = (int)shift % 32; int inLength = pResult.m_length; // RJ_ASSERT( inLength + shiftBlocks <= c_BigInt_MaxBlocks ); // check if the shift is block aligned if (shiftBits == 0) { // process blocks high to low so that we can safely process in place fixed (uint* pInBlocks1 = pResult.m_blocks) { uint* pInBlocks = pInBlocks1; uint* pInCur = pInBlocks + inLength - 1; uint* pOutCur = pInCur + shiftBlocks; // copy blocks from high to low for (; pInCur >= pInBlocks; --pInCur, --pOutCur) { *pOutCur = *pInCur; } } // zero the remaining low blocks for ( uint i = 0; i < shiftBlocks; ++i) pResult.m_blocks[i] = 0; pResult.m_length += shiftBlocks; } // else we need to shift partial blocks else { int inBlockIdx = inLength - 1; int outBlockIdx = inLength + shiftBlocks; // set the length to hold the shifted blocks //RJ_ASSERT( outBlockIdx < c_BigInt_MaxBlocks ); pResult.m_length = outBlockIdx + 1; // output the initial blocks int lowBitsShift = (32 - shiftBits); uint highBits = 0; uint block = pResult.m_blocks[inBlockIdx]; uint lowBits = block >> lowBitsShift; while ( inBlockIdx > 0 ) { pResult.m_blocks[outBlockIdx] = highBits | lowBits; highBits = block << shiftBits; --inBlockIdx; --outBlockIdx; block = pResult.m_blocks[inBlockIdx]; lowBits = block >> lowBitsShift; } // output the final blocks // RJ_ASSERT( outBlockIdx == shiftBlocks + 1 ); pResult.m_blocks[outBlockIdx] = highBits | lowBits; pResult.m_blocks[outBlockIdx-1] = block << shiftBits; // zero the remaining low blocks for ( uint i = 0; i < shiftBlocks; ++i) pResult.m_blocks[i] = 0; // check if the terminating block has no set bits if (pResult.m_blocks[pResult.m_length - 1] == 0) --pResult.m_length; } } //****************************************************************************** // Different modes for terminating digit output //****************************************************************************** public enum CutoffMode { Unique, // as many digits as necessary to print a uniquely identifiable number TotalLength, // up to cutoffNumber significant digits FractionLength, // up to cutoffNumber significant digits past the decimal point }; //****************************************************************************** // This is an implementation the Dragon4 algorithm to convert a binary number // in floating point format to a decimal number in string format. The function // returns the number of digits written to the output buffer and the output is // not NUL terminated. // // The floating point input value is (mantissa * 2^exponent). // // See the following papers for more information on the algorithm: // "How to Print Floating-Point Numbers Accurately" // Steele and White // http://kurtstephens.com/files/p372-steele.pdf // "Printing Floating-Point Numbers Quickly and Accurately" // Burger and Dybvig // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.72.4656&rep=rep1&type=pdf //****************************************************************************** private static unsafe uint Dragon4 ( ulong mantissa, // value significand int exponent, // value exponent in base 2 uint mantissaHighBitIdx, // index of the highest set mantissa bit bool hasUnequalMargins, // is the high margin twice as large as the low margin CutoffMode cutoffMode, // how to determine output length uint cutoffNumber, // parameter to the selected cutoffMode byte* pOutBuffer, // buffer to output into uint bufferSize, // maximum characters that can be printed to pOutBuffer out int pOutExponent // the base 10 exponent of the first digit ) { byte* pCurDigit = pOutBuffer; // RJ_ASSERT( bufferSize > 0 ); // if the mantissa is zero, the value is zero regardless of the exponent if (mantissa == 0) { *pCurDigit = (byte)'0'; pOutExponent = 0; return 1; } // compute the initial state in integral form such that // value = scaledValue / scale // marginLow = scaledMarginLow / scale tBigInt scale = default; // positive scale applied to value and margin such that they can be // represented as whole numbers tBigInt scaledValue = default; // scale * mantissa tBigInt scaledMarginLow = default; // scale * 0.5 * (distance between this floating-point number and its // immediate lower value) // For normalized IEEE floating point values, each time the exponent is incremented the margin also // doubles. That creates a subset of transition numbers where the high margin is twice the size of // the low margin. tBigInt * pScaledMarginHigh; tBigInt optionalMarginHigh = default; if ( hasUnequalMargins ) { // if we have no fractional component if (exponent > 0) { // 1) Expand the input value by multiplying out the mantissa and exponent. This represents // the input value in its whole number representation. // 2) Apply an additional scale of 2 such that later comparisons against the margin values // are simplified. // 3) Set the margin value to the lowest mantissa bit's scale. // scaledValue = 2 * 2 * mantissa*2^exponent scaledValue.SetU64( 4 * mantissa ); BigInt_ShiftLeft(ref scaledValue, (uint)exponent); // scale = 2 * 2 * 1 scale.SetU32( 4 ); // scaledMarginLow = 2 * 2^(exponent-1) BigInt_Pow2( out scaledMarginLow, (uint)exponent ); // scaledMarginHigh = 2 * 2 * 2^(exponent-1) BigInt_Pow2( out optionalMarginHigh, (uint)(exponent + 1)); } // else we have a fractional exponent else { // In order to track the mantissa data as an integer, we store it as is with a large scale // scaledValue = 2 * 2 * mantissa scaledValue.SetU64( 4 * mantissa ); // scale = 2 * 2 * 2^(-exponent) BigInt_Pow2(out scale, (uint)(-exponent + 2)); // scaledMarginLow = 2 * 2^(-1) scaledMarginLow.SetU32( 1 ); // scaledMarginHigh = 2 * 2 * 2^(-1) optionalMarginHigh.SetU32( 2 ); } // the high and low margins are different pScaledMarginHigh = &optionalMarginHigh; } else { // if we have no fractional component if (exponent > 0) { // 1) Expand the input value by multiplying out the mantissa and exponent. This represents // the input value in its whole number representation. // 2) Apply an additional scale of 2 such that later comparisons against the margin values // are simplified. // 3) Set the margin value to the lowest mantissa bit's scale. // scaledValue = 2 * mantissa*2^exponent scaledValue.SetU64( 2 * mantissa ); BigInt_ShiftLeft(ref scaledValue, (uint)exponent); // scale = 2 * 1 scale.SetU32( 2 ); // scaledMarginLow = 2 * 2^(exponent-1) BigInt_Pow2(out scaledMarginLow, (uint)exponent ); } // else we have a fractional exponent else { // In order to track the mantissa data as an integer, we store it as is with a large scale // scaledValue = 2 * mantissa scaledValue.SetU64( 2 * mantissa ); // scale = 2 * 2^(-exponent) BigInt_Pow2(out scale, (uint)(-exponent + 1)); // scaledMarginLow = 2 * 2^(-1) scaledMarginLow.SetU32( 1 ); } // the high and low margins are equal pScaledMarginHigh = &scaledMarginLow; } // Compute an estimate for digitExponent that will be correct or undershoot by one. // This optimization is based on the paper "Printing Floating-Point Numbers Quickly and Accurately" // by Burger and Dybvig http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.72.4656&rep=rep1&type=pdf // We perform an additional subtraction of 0.69 to increase the frequency of a failed estimate // because that lets us take a faster branch in the code. 0.69 is chosen because 0.69 + log10(2) is // less than one by a reasonable epsilon that will account for any floating point error. // // We want to set digitExponent to floor(log10(v)) + 1 // v = mantissa*2^exponent // log2(v) = log2(mantissa) + exponent; // log10(v) = log2(v) * log10(2) // floor(log2(v)) = mantissaHighBitIdx + exponent; // log10(v) - log10(2) < (mantissaHighBitIdx + exponent) * log10(2) <= log10(v) // log10(v) < (mantissaHighBitIdx + exponent) * log10(2) + log10(2) <= log10(v) + log10(2) // floor( log10(v) ) < ceil( (mantissaHighBitIdx + exponent) * log10(2) ) <= floor( log10(v) ) + 1 const double log10_2 = 0.30102999566398119521373889472449; var digitExponentDoubleValue = (double) ((int) mantissaHighBitIdx + exponent) * log10_2 - 0.69; digitExponentDoubleValue = Math.Ceiling(digitExponentDoubleValue); int digitExponent = (int)digitExponentDoubleValue; // if the digit exponent is smaller than the smallest desired digit for fractional cutoff, // pull the digit back into legal range at which point we will round to the appropriate value. // Note that while our value for digitExponent is still an estimate, this is safe because it // only increases the number. This will either correct digitExponent to an accurate value or it // will clamp it above the accurate value. if (cutoffMode == CutoffMode.FractionLength && digitExponent <= -(int)cutoffNumber) { digitExponent = -(int)cutoffNumber + 1; } // Divide value by 10^digitExponent. if (digitExponent > 0) { // The exponent is positive creating a division so we multiply up the scale. tBigInt temp; BigInt_MultiplyPow10( out temp, scale, (uint)digitExponent ); scale = temp; } else if (digitExponent < 0) { // The exponent is negative creating a multiplication so we multiply up the scaledValue, // scaledMarginLow and scaledMarginHigh. tBigInt pow10; BigInt_Pow10(out pow10, (uint)(-digitExponent)); tBigInt temp; BigInt_Multiply( out temp, scaledValue, pow10); scaledValue = temp; BigInt_Multiply( out temp, scaledMarginLow, pow10); scaledMarginLow = temp; if (pScaledMarginHigh != &scaledMarginLow) BigInt_Multiply2( out *pScaledMarginHigh, scaledMarginLow ); } // If (value >= 1), our estimate for digitExponent was too low if( BigInt_Compare(scaledValue,scale) >= 0 ) { // The exponent estimate was incorrect. // Increment the exponent and don't perform the premultiply needed // for the first loop iteration. digitExponent = digitExponent + 1; } else { // The exponent estimate was correct. // Multiply larger by the output base to prepare for the first loop iteration. BigInt_Multiply10( ref scaledValue ); BigInt_Multiply10( ref scaledMarginLow ); if (pScaledMarginHigh != &scaledMarginLow) BigInt_Multiply2( out *pScaledMarginHigh, scaledMarginLow ); } // Compute the cutoff exponent (the exponent of the final digit to print). // Default to the maximum size of the output buffer. int cutoffExponent = digitExponent - (int)bufferSize; switch (cutoffMode) { // print digits until we pass the accuracy margin limits or buffer size case CutoffMode.Unique: break; // print cutoffNumber of digits or until we reach the buffer size case CutoffMode.TotalLength: { int desiredCutoffExponent = digitExponent - (int) cutoffNumber; if (desiredCutoffExponent > cutoffExponent) cutoffExponent = desiredCutoffExponent; } break; // print cutoffNumber digits past the decimal point or until we reach the buffer size case CutoffMode.FractionLength: { int desiredCutoffExponent = -(int) cutoffNumber; if (desiredCutoffExponent > cutoffExponent) cutoffExponent = desiredCutoffExponent; } break; } // Output the exponent of the first digit we will print pOutExponent = digitExponent-1; // In preparation for calling BigInt_DivideWithRemainder_MaxQuotient9(), // we need to scale up our values such that the highest block of the denominator // is greater than or equal to 8. We also need to guarantee that the numerator // can never have a length greater than the denominator after each loop iteration. // This requires the highest block of the denominator to be less than or equal to // 429496729 which is the highest number that can be multiplied by 10 without // overflowing to a new block. // RJ_ASSERT( scale.GetLength() > 0 ); uint hiBlock = scale.GetBlock( scale.GetLength() - 1 ); if (hiBlock < 8 || hiBlock > 429496729) { // Perform a bit shift on all values to get the highest block of the denominator into // the range [8,429496729]. We are more likely to make accurate quotient estimations // in BigInt_DivideWithRemainder_MaxQuotient9() with higher denominator values so // we shift the denominator to place the highest bit at index 27 of the highest block. // This is safe because (2^28 - 1) = 268435455 which is less than 429496729. This means // that all values with a highest bit at index 27 are within range. uint hiBlockLog2 = LogBase2(hiBlock); // RJ_ASSERT(hiBlockLog2 < 3 || hiBlockLog2 > 27); uint shift = (32 + 27 - hiBlockLog2) % 32; BigInt_ShiftLeft( ref scale, shift ); BigInt_ShiftLeft( ref scaledValue, shift); BigInt_ShiftLeft( ref scaledMarginLow, shift); if (pScaledMarginHigh != &scaledMarginLow) BigInt_Multiply2( out *pScaledMarginHigh, scaledMarginLow ); } // These values are used to inspect why the print loop terminated so we can properly // round the final digit. bool low; // did the value get within marginLow distance from zero bool high; // did the value get within marginHigh distance from one uint outputDigit; // current digit being output if (cutoffMode == CutoffMode.Unique) { // For the unique cutoff mode, we will try to print until we have reached a level of // precision that uniquely distinguishes this value from its neighbors. If we run // out of space in the output buffer, we terminate early. for (;;) { digitExponent = digitExponent-1; // divide out the scale to extract the digit outputDigit = BigInt_DivideWithRemainder_MaxQuotient9(ref scaledValue, scale); //RJ_ASSERT( outputDigit < 10 ); // update the high end of the value tBigInt scaledValueHigh; BigInt_Add( out scaledValueHigh, scaledValue, *pScaledMarginHigh ); // stop looping if we are far enough away from our neighboring values // or if we have reached the cutoff digit low = BigInt_Compare(scaledValue, scaledMarginLow) < 0; high = BigInt_Compare(scaledValueHigh, scale) > 0; if (low | high | (digitExponent == cutoffExponent)) break; // store the output digit *pCurDigit = (byte)('0' + outputDigit); ++pCurDigit; // multiply larger by the output base BigInt_Multiply10( ref scaledValue ); BigInt_Multiply10( ref scaledMarginLow ); if (pScaledMarginHigh != &scaledMarginLow) BigInt_Multiply2( out *pScaledMarginHigh, scaledMarginLow ); } } else { // For length based cutoff modes, we will try to print until we // have exhausted all precision (i.e. all remaining digits are zeros) or // until we reach the desired cutoff digit. low = false; high = false; for (;;) { digitExponent = digitExponent-1; // divide out the scale to extract the digit outputDigit = BigInt_DivideWithRemainder_MaxQuotient9(ref scaledValue, scale); //RJ_ASSERT( outputDigit < 10 ); if ( scaledValue.IsZero() | (digitExponent == cutoffExponent) ) break; // store the output digit *pCurDigit = (byte)('0' + outputDigit); ++pCurDigit; // multiply larger by the output base BigInt_Multiply10(ref scaledValue); } } // round off the final digit // default to rounding down if value got too close to 0 bool roundDown = low; // if it is legal to round up and down if (low == high) { // round to the closest digit by comparing value with 0.5. To do this we need to convert // the inequality to large integer values. // compare( value, 0.5 ) // compare( scale * value, scale * 0.5 ) // compare( 2 * scale * value, scale ) BigInt_Multiply2(ref scaledValue); int compare = BigInt_Compare(scaledValue, scale); roundDown = compare < 0; // if we are directly in the middle, round towards the even digit (i.e. IEEE rouding rules) if (compare == 0) roundDown = (outputDigit & 1) == 0; } // print the rounded digit if (roundDown) { *pCurDigit = (byte)('0' + outputDigit); ++pCurDigit; } else { // handle rounding up if (outputDigit == 9) { // find the first non-nine prior digit for (;;) { // if we are at the first digit if (pCurDigit == pOutBuffer) { // output 1 at the next highest exponent *pCurDigit = (byte)'1'; ++pCurDigit; pOutExponent += 1; break; } --pCurDigit; if (*pCurDigit != (byte)'9') { // increment the digit *pCurDigit += 1; ++pCurDigit; break; } } } else { // values in the range [0,8] can perform a simple round up *pCurDigit = (byte)((byte)'0' + outputDigit + 1); ++pCurDigit; } } // return the number of digits output uint outputLen = (uint)(pCurDigit - pOutBuffer); // RJ_ASSERT(outputLen <= bufferSize); return outputLen; } //****************************************************************************** //****************************************************************************** public enum PrintFloatFormat { Positional, // [-]ddddd.dddd Scientific, // [-]d.dddde[sign]ddd } //******************************************************************************\ // Helper union to decompose a 32-bit IEEE float. // sign: 1 bit // exponent: 8 bits // mantissa: 23 bits //****************************************************************************** [StructLayout(LayoutKind.Explicit)] public struct tFloatUnion32 { public bool IsNegative() { return (m_integer >> 31) != 0; } public uint GetExponent() { return (m_integer >> 23) & 0xFF; } public uint GetMantissa() { return m_integer & 0x7FFFFF; } [FieldOffset(0)] public float m_floatingPoint; [FieldOffset(0)] public uint m_integer; }; //****************************************************************************** // Helper union to decompose a 64-bit IEEE float. // sign: 1 bit // exponent: 11 bits // mantissa: 52 bits //****************************************************************************** [StructLayout(LayoutKind.Explicit)] public struct tFloatUnion64 { public bool IsNegative() { return (m_integer >> 63) != 0; } public uint GetExponent() { return (uint)((m_integer >> 52) & 0x7FF); } public ulong GetMantissa() { return m_integer & 0xFFFFFFFFFFFFFUL; } [FieldOffset(0)] public double m_floatingPoint; [FieldOffset(0)] public ulong m_integer; }; //****************************************************************************** // Outputs the positive number with positional notation: ddddd.dddd // The output is always NUL terminated and the output length (not including the // NUL) is returned. //****************************************************************************** private static unsafe int FormatPositional ( byte* pOutBuffer, // buffer to output into uint bufferSize, // maximum characters that can be printed to pOutBuffer ulong mantissa, // value significand int exponent, // value exponent in base 2 uint mantissaHighBitIdx, // index of the highest set mantissa bit bool hasUnequalMargins, // is the high margin twice as large as the low margin int precision // Negative prints as many digits as are needed for a unique // number. Positive specifies the maximum number of // significant digits to print past the decimal point. ) { //RJ_ASSERT(bufferSize > 0); int printExponent; uint numPrintDigits; uint maxPrintLen = bufferSize - 1; if (precision < 0) { numPrintDigits = Dragon4(mantissa, exponent, mantissaHighBitIdx, hasUnequalMargins, CutoffMode.Unique, 0, pOutBuffer, maxPrintLen, out printExponent); } else { numPrintDigits = Dragon4(mantissa, exponent, mantissaHighBitIdx, hasUnequalMargins, CutoffMode.FractionLength, (uint)precision, pOutBuffer, maxPrintLen, out printExponent); } //RJ_ASSERT(numPrintDigits > 0); //RJ_ASSERT(numPrintDigits <= bufferSize); // track the number of digits past the decimal point that have been printed uint numFractionDigits = 0; // if output has a whole number if (printExponent >= 0) { // leave the whole number at the start of the buffer uint numWholeDigits = (uint)(printExponent + 1); if (numPrintDigits < numWholeDigits) { // don't overflow the buffer if (numWholeDigits > maxPrintLen) numWholeDigits = maxPrintLen; // add trailing zeros up to the decimal point for (; numPrintDigits < numWholeDigits; ++numPrintDigits) pOutBuffer[numPrintDigits] = (byte)'0'; } // insert the decimal point prior to the fraction else if (numPrintDigits > (uint)numWholeDigits) { numFractionDigits = numPrintDigits - numWholeDigits; uint maxFractionDigits = maxPrintLen - numWholeDigits - 1; if (numFractionDigits > maxFractionDigits) numFractionDigits = maxFractionDigits; #if !UNITY_DOTSRUNTIME && !NET_DOTS Unsafe.CopyBlock(pOutBuffer + numWholeDigits + 1, pOutBuffer + numWholeDigits, numFractionDigits); #else UnsafeUtility.MemCpy(pOutBuffer + numWholeDigits + 1, pOutBuffer + numWholeDigits, numFractionDigits); #endif pOutBuffer[numWholeDigits] = (byte)'.'; numPrintDigits = numWholeDigits + 1 + numFractionDigits; } } else { // shift out the fraction to make room for the leading zeros if (maxPrintLen > 2) { uint numFractionZeros = (uint)( - printExponent - 1); uint maxFractionZeros = maxPrintLen - 2; if (numFractionZeros > maxFractionZeros) numFractionZeros = maxFractionZeros; uint digitsStartIdx = 2 + numFractionZeros; // shift the significant digits right such that there is room for leading zeros numFractionDigits = numPrintDigits; uint maxFractionDigits = maxPrintLen - digitsStartIdx; if (numFractionDigits > maxFractionDigits) numFractionDigits = maxFractionDigits; #if !UNITY_DOTSRUNTIME && !NET_DOTS Unsafe.CopyBlock(pOutBuffer + digitsStartIdx, pOutBuffer, numFractionDigits); #else UnsafeUtility.MemCpy(pOutBuffer + digitsStartIdx, pOutBuffer, numFractionDigits); #endif // insert the leading zeros for (uint i = 2; i < digitsStartIdx; ++i) pOutBuffer[i] = (byte)'0'; // update the counts numFractionDigits += numFractionZeros; numPrintDigits = numFractionDigits; } // add the decimal point if (maxPrintLen > 1) { pOutBuffer[1] = (byte)'.'; numPrintDigits += 1; } // add the initial zero if (maxPrintLen > 0) { pOutBuffer[0] = (byte)'0'; numPrintDigits += 1; } } // add trailing zeros up to precision length if (precision > (int)numFractionDigits && numPrintDigits < maxPrintLen) { // add a decimal point if this is the first fractional digit we are printing if (numFractionDigits == 0) { pOutBuffer[numPrintDigits++] = (byte)'.'; } // compute the number of trailing zeros needed uint totalDigits = (uint)(numPrintDigits + (precision - (int)numFractionDigits)); if (totalDigits > maxPrintLen) totalDigits = maxPrintLen; for (; numPrintDigits < totalDigits; ++numPrintDigits) pOutBuffer[numPrintDigits] = (byte)'0'; } // terminate the buffer //RJ_ASSERT(numPrintDigits <= maxPrintLen); //pOutBuffer[numPrintDigits] = '\0'; return (int)numPrintDigits; } //****************************************************************************** // Outputs the positive number with scientific notation: d.dddde[sign]ddd // The output is always NUL terminated and the output length (not including the // NUL) is returned. //****************************************************************************** private static unsafe int FormatScientific ( byte* pOutBuffer, // buffer to output into uint bufferSize, // maximum characters that can be printed to pOutBuffer ulong mantissa, // value significand int exponent, // value exponent in base 2 uint mantissaHighBitIdx, // index of the highest set mantissa bit bool hasUnequalMargins, // is the high margin twice as large as the low margin int precision // Negative prints as many digits as are needed for a unique // number. Positive specifies the maximum number of // significant digits to print past the decimal point. ) { //RJ_ASSERT(bufferSize > 0); int printExponent; uint numPrintDigits; if (precision < 0) { numPrintDigits = Dragon4(mantissa, exponent, mantissaHighBitIdx, hasUnequalMargins, CutoffMode.Unique, 0, pOutBuffer, bufferSize, out printExponent); } else { numPrintDigits = Dragon4(mantissa, exponent, mantissaHighBitIdx, hasUnequalMargins, CutoffMode.TotalLength, (uint)(precision + 1), pOutBuffer, bufferSize, out printExponent); } //RJ_ASSERT(numPrintDigits > 0); //RJ_ASSERT(numPrintDigits <= bufferSize); byte* pCurOut = pOutBuffer; // keep the whole number as the first digit if (bufferSize > 1) { pCurOut += 1; bufferSize -= 1; } // insert the decimal point prior to the fractional number uint numFractionDigits = numPrintDigits - 1; if (numFractionDigits > 0 && bufferSize > 1) { uint maxFractionDigits = bufferSize - 2; if (numFractionDigits > maxFractionDigits) numFractionDigits = maxFractionDigits; #if !UNITY_DOTSRUNTIME && !NET_DOTS Unsafe.CopyBlock(pCurOut + 1, pCurOut, numFractionDigits); #else UnsafeUtility.MemCpy(pCurOut + 1, pCurOut, numFractionDigits); #endif pCurOut[0] = (byte)'.'; pCurOut += (1 + numFractionDigits); bufferSize -= (1 + numFractionDigits); } // add trailing zeros up to precision length if (precision > (int)numFractionDigits && bufferSize > 1) { // add a decimal point if this is the first fractional digit we are printing if (numFractionDigits == 0) { *pCurOut = (byte)'.'; ++pCurOut; --bufferSize; } // compute the number of trailing zeros needed uint numZeros = (uint)(precision - numFractionDigits); if (numZeros > bufferSize - 1) numZeros = bufferSize - 1; for (byte* pEnd = pCurOut + numZeros; pCurOut < pEnd; ++pCurOut) *pCurOut = (byte)'0'; } // print the exponent into a local buffer and copy into output buffer if (bufferSize > 1) { var exponentBuffer = stackalloc byte[5]; exponentBuffer[0] = (byte)'e'; if (printExponent >= 0) { exponentBuffer[1] = (byte)'+'; } else { exponentBuffer[1] = (byte)'-'; printExponent = -printExponent; } //RJ_ASSERT(printExponent < 1000); uint hundredsPlace = (uint)(printExponent / 100); uint tensPlace = (uint)((printExponent - hundredsPlace * 100) / 10); uint onesPlace = (uint)((printExponent - hundredsPlace * 100 - tensPlace * 10)); exponentBuffer[2] = (byte)('0' + hundredsPlace); exponentBuffer[3] = (byte)('0' + tensPlace); exponentBuffer[4] = (byte)('0' + onesPlace); // copy the exponent buffer into the output uint maxExponentSize = bufferSize - 1; uint exponentSize = (5 < maxExponentSize) ? 5 : maxExponentSize; #if !UNITY_DOTSRUNTIME && !NET_DOTS Unsafe.CopyBlock(pCurOut, exponentBuffer, exponentSize); #else UnsafeUtility.MemCpy(pCurOut, exponentBuffer, exponentSize); #endif pCurOut += exponentSize; bufferSize -= exponentSize; } //RJ_ASSERT(bufferSize > 0); //pCurOut[0] = '\0'; return (int)(pCurOut - pOutBuffer); } //****************************************************************************** // Print special case values for infinities and NaNs. // The output string is always NUL terminated and the string length (not // including the NUL) is returned. //****************************************************************************** private static readonly byte[] InfinityString = new byte[] { (byte) 'I', (byte) 'n', (byte) 'f', (byte) 'i', (byte) 'n', (byte) 'i', (byte) 't', (byte) 'y', }; private static readonly byte[] NanString = new byte[] { (byte) 'N', (byte) 'a', (byte) 'N', }; private static unsafe void FormatInfinityNaN(byte* dest, ref int destIndex, int destLength, ulong mantissa, bool isNegative, FormatOptions formatOptions) { //RJ_ASSERT(bufferSize > 0); int length = mantissa == 0 ? 8 + (isNegative ? 1 : 0) : 3; int align = formatOptions.AlignAndSize; // left align if (AlignLeft(dest, ref destIndex, destLength, align, length)) return; // Check for infinity if (mantissa == 0) { if (isNegative) { if (destIndex >= destLength) return; dest[destIndex++] = (byte)'-'; } for (int i = 0; i < 8; i++) { if (destIndex >= destLength) return; dest[destIndex++] = InfinityString[i]; } } else { for (int i = 0; i < 3; i++) { if (destIndex >= destLength) return; dest[destIndex++] = NanString[i]; } } // right align AlignRight(dest, ref destIndex, destLength, align, length); } // ------------------------------------------------------------------------------ // Part of the following code is taking some constants and code from // https://github.com/dotnet/runtime/blob/75036ffec9473dd1d948c052c041fdedd7784ac9/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the https://github.com/dotnet/runtime/blob/master/LICENSE.TXT file for more information. // ------------------------------------------------------------------------------ // SinglePrecision and DoublePrecision represent the maximum number of digits required // to guarantee that any given Single or Double can roundtrip. Some numbers may require // less, but none will require more. private const int SinglePrecision = 9; private const int DoublePrecision = 17; internal const int SingleNumberBufferLength = SinglePrecision + 1; // + zero internal const int DoubleNumberBufferLength = DoublePrecision + 1; // + zero // SinglePrecisionCustomFormat and DoublePrecisionCustomFormat are used to ensure that // custom format strings return the same string as in previous releases when the format // would return x digits or less (where x is the value of the corresponding constant). // In order to support more digits, we would need to update ParseFormatSpecifier to pre-parse // the format and determine exactly how many digits are being requested and whether they // represent "significant digits" or "digits after the decimal point". private const int SinglePrecisionCustomFormat = 7; private const int DoublePrecisionCustomFormat = 15; /// /// Format a float 32-bit to a general format to the specified destination buffer. /// /// Destination buffer. /// Current index in destination buffer. /// Maximum length of destination buffer. /// The float 32 value to format. /// Formatting options. [MethodImpl(MethodImplOptions.NoInlining)] private static unsafe void ConvertFloatToString(byte* dest, ref int destIndex, int destLength, float value, FormatOptions formatOptions) { // deconstruct the floating point value tFloatUnion32 floatUnion = default; floatUnion.m_floatingPoint = value; uint floatExponent = floatUnion.GetExponent(); uint floatMantissa = floatUnion.GetMantissa(); // if this is a special value if (floatExponent == 0xFF) { FormatInfinityNaN(dest, ref destIndex, destLength, floatMantissa, floatUnion.IsNegative(), formatOptions); } // else this is a number else { // factor the value into its parts uint mantissa; int exponent; uint mantissaHighBitIdx; bool hasUnequalMargins; if (floatExponent != 0) { // normalized // The floating point equation is: // value = (1 + mantissa/2^23) * 2 ^ (exponent-127) // We convert the integer equation by factoring a 2^23 out of the exponent // value = (1 + mantissa/2^23) * 2^23 * 2 ^ (exponent-127-23) // value = (2^23 + mantissa) * 2 ^ (exponent-127-23) // Because of the implied 1 in front of the mantissa we have 24 bits of precision. // m = (2^23 + mantissa) // e = (exponent-127-23) mantissa = (uint)((1UL << 23) | floatMantissa); exponent = (int)(floatExponent - 127 - 23); mantissaHighBitIdx = 23; hasUnequalMargins = (floatExponent != 1) && (floatMantissa == 0); } else { // denormalized // The floating point equation is: // value = (mantissa/2^23) * 2 ^ (1-127) // We convert the integer equation by factoring a 2^23 out of the exponent // value = (mantissa/2^23) * 2^23 * 2 ^ (1-127-23) // value = mantissa * 2 ^ (1-127-23) // We have up to 23 bits of precision. // m = (mantissa) // e = (1-127-23) mantissa = floatMantissa; exponent = 1 - 127 - 23; mantissaHighBitIdx = LogBase2(mantissa); hasUnequalMargins = false; } var precision = formatOptions.Specifier == 0 ? -1 : formatOptions.Specifier; var bufferSize = Math.Max(SingleNumberBufferLength, precision + 1); var pOutBuffer = stackalloc byte[bufferSize]; if (precision < 0) { precision = SinglePrecisionCustomFormat; } int printExponent; uint numPrintDigits = Dragon4(mantissa, exponent, mantissaHighBitIdx, hasUnequalMargins, CutoffMode.TotalLength, (uint)precision, pOutBuffer, (uint)(bufferSize - 1), out printExponent); pOutBuffer[numPrintDigits] = 0; // Negative 0 are displayed as 0 bool isNegative = floatUnion.IsNegative(); if (floatUnion.m_integer == ((uint)1 << 31)) { isNegative = false; } var number = new NumberBuffer(NumberBufferKind.Float, pOutBuffer, (int)numPrintDigits, printExponent + 1, isNegative); FormatNumber(dest, ref destIndex, destLength, ref number, precision, formatOptions); } } /// /// Format a float 64-bit to a general format to the specified destination buffer. /// /// Destination buffer. /// Current index in destination buffer. /// Maximum length of destination buffer. /// The float 64 value to format. /// Formatting options. [MethodImpl(MethodImplOptions.NoInlining)] private static unsafe void ConvertDoubleToString(byte* dest, ref int destIndex, int destLength, double value, FormatOptions formatOptions) { // deconstruct the floating point value tFloatUnion64 floatUnion = default; floatUnion.m_floatingPoint = value; uint floatExponent = floatUnion.GetExponent(); ulong floatMantissa = floatUnion.GetMantissa(); // if this is a special value if (floatExponent == 0x7FF) { FormatInfinityNaN(dest, ref destIndex, destLength, floatMantissa, floatUnion.IsNegative(), formatOptions); } // else this is a number else { // factor the value into its parts ulong mantissa; int exponent; uint mantissaHighBitIdx; bool hasUnequalMargins; if (floatExponent != 0) { // normal // The floating point equation is: // value = (1 + mantissa/2^52) * 2 ^ (exponent-1023) // We convert the integer equation by factoring a 2^52 out of the exponent // value = (1 + mantissa/2^52) * 2^52 * 2 ^ (exponent-1023-52) // value = (2^52 + mantissa) * 2 ^ (exponent-1023-52) // Because of the implied 1 in front of the mantissa we have 53 bits of precision. // m = (2^52 + mantissa) // e = (exponent-1023+1-53) mantissa = (1UL << 52) | floatMantissa; exponent = (int)(floatExponent - 1023 - 52); mantissaHighBitIdx = 52; hasUnequalMargins = (floatExponent != 1) && (floatMantissa == 0); } else { // subnormal // The floating point equation is: // value = (mantissa/2^52) * 2 ^ (1-1023) // We convert the integer equation by factoring a 2^52 out of the exponent // value = (mantissa/2^52) * 2^52 * 2 ^ (1-1023-52) // value = mantissa * 2 ^ (1-1023-52) // We have up to 52 bits of precision. // m = (mantissa) // e = (1-1023-52) mantissa = floatMantissa; exponent = 1 - 1023 - 52; mantissaHighBitIdx = LogBase2((uint)mantissa); hasUnequalMargins = false; } var precision = formatOptions.Specifier == 0 ? -1 : formatOptions.Specifier; var bufferSize = Math.Max(DoubleNumberBufferLength, precision + 1); var pOutBuffer = stackalloc byte[bufferSize]; if (precision < 0) { precision = DoublePrecisionCustomFormat; } int printExponent; uint numPrintDigits = Dragon4(mantissa, exponent, mantissaHighBitIdx, hasUnequalMargins, CutoffMode.TotalLength, (uint)precision, pOutBuffer, (uint)(bufferSize - 1), out printExponent); pOutBuffer[numPrintDigits] = 0; // Negative 0 are displayed as 0 bool isNegative = floatUnion.IsNegative(); if (floatUnion.m_integer == ((ulong)1 << 63)) { isNegative = false; } var number = new NumberBuffer(NumberBufferKind.Float, pOutBuffer, (int)numPrintDigits, printExponent + 1, isNegative); FormatNumber(dest, ref destIndex, destLength, ref number, precision, formatOptions); } } } }