You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1947 lines
83 KiB
1947 lines
83 KiB
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; |
|
|
|
/// <summary> |
|
/// Format a float 32-bit to a general format to the specified destination buffer. |
|
/// </summary> |
|
/// <param name="dest">Destination buffer.</param> |
|
/// <param name="destIndex">Current index in destination buffer.</param> |
|
/// <param name="destLength">Maximum length of destination buffer.</param> |
|
/// <param name="value">The float 32 value to format.</param> |
|
/// <param name="formatOptions">Formatting options.</param> |
|
[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); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Format a float 64-bit to a general format to the specified destination buffer. |
|
/// </summary> |
|
/// <param name="dest">Destination buffer.</param> |
|
/// <param name="destIndex">Current index in destination buffer.</param> |
|
/// <param name="destLength">Maximum length of destination buffer.</param> |
|
/// <param name="value">The float 64 value to format.</param> |
|
/// <param name="formatOptions">Formatting options.</param> |
|
[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); |
|
} |
|
} |
|
} |
|
}
|
|
|