![](/img/trans.png)
[英]Converting float/double/decimal to (u)int, (u)long in C# without memory allocation
[英]Convert a float to char[] without memory allocation
您知道如何在不分配任何內存的情況下將float轉換為chars緩沖區嗎?
=>我只是想重新做與float.ToString()一樣的事情; 所以我可以將結果放入緩沖區,而不是分配字符串
我寫了一個函數,但是它不能很好地處理“四舍五入”:
這是因為39.71作為浮點數是對內存中存儲的值39.709996的舍入。 通過對函數進行四舍五入,我可以輕松得出如下結果:
這也不是什么float.ToString()
,因為我想保持與float.ToString()
完全相同的算法,該算法設法編寫“ 39.71”和“ 39.71001”
您知道這個float.ToString()
如何工作嗎?
我的目標很精確:我想在一個很大的字符串中附加大量的float(與其他類型混合),並在最后只分配一次此字符串,以避免過多的垃圾回收。 因此,我確實需要將float轉換為char數組(無論確切的類型格式,只有不可變的字符串)
您可以致電C stdlib sprintf()
和朋友來解決大多數格式和數據問題。 有關示例,請參見https://stackoverflow.com/a/2479210/3150802 。 通過評估sprintf()
的返回值來跟蹤打印位置(即char數組的索引sprintf()
將很重要。
跨越管理/非管理邊界會不可避免地帶來一些性能損失。 一種可能的緩解策略是減少此類交叉的次數,例如,通過編寫一個C包裝函數,該函數可以接收大量的float並使用s*printf()
一次性將其全部寫入。
如果數據浮點數和地址在兩個方面都是位相同的,那么對於數據封送(即CLI和本機表示形式之間的來回轉換)的潛在損失可能不會像浮點數和地址這樣的POD產生問題。
從您的評論看來,您希望能夠將許多浮點數(和其他值類型)格式化為非常大的字符數組,而不必進行大量的內存分配。
不可能避免所有內存分配,但是可以使用StringBuilder.AppendFormat()
和StringBuilder.CopyTo()
來最小化它們。
如果您對最終char數組的最大長度有所了解,則可以初始化一個StringBuilder
,其容量足以容納它。 這可能會減少內存分配的數量,但是如果您使緩沖區過大,則會浪費內存。
代碼如下所示:
int capacity = 8192; // If you have some idea of the final string length.
var sb = new StringBuilder(capacity);
for (float x = 0; x < 1000; x += 1.2345f)
sb.AppendFormat(", {0}", x);
char[] array = new char[sb.Length];
sb.CopyTo(0, array, 0, sb.Length); // Now array[] contains the result.
請注意,這里的緩沖區分配的最小數量為兩個:一個為StringBuilder
使用的內部緩沖區,另一個為char []數組。
但是,很有可能在幕后進行許多其他的小分配-但是由於GC的工作方式,它們不太可能進入任何第1代收藏中,因此不太可能導致性能下降。問題。
由於Marc Gravell提供的float.ToString()源代碼,以下是我最終編寫的解決方案。
它經過了簡化,但目前看來效果很好(也許我錯過了一些特殊情況)。 我看到的唯一主要區別是,像5.34E-05這樣的很小的浮點數基本上將被寫為0.0000534(實際上,我更喜歡這樣)
有關信息,請參見我的完整StringFast類(Unity C#代碼): http ://pastebin.com/HqAw2pTG。 以及一些使用它的測試: http : //pastebin.com/brynBFyC
該測試運行1000次以下操作:4個追加(2個字符串,一個浮點數和一個int)和1個字符串替換。 我使用帶有+和Concat()的字符串,使用StringBuilder以及我的StringFast類來運行測試。 當然,對於最后兩個,我不會每次都重新創建它們。
以下是結果以及內存分配和時間:
如果我用100個浮點數的串聯代替5個操作:
如您所見,StringBuilder並不是很好,特別是當字符串上只有幾個操作時。 StringFast類的分配僅由我做的最終ToString()引起(以便能夠將字符串與其他函數一起使用)
這是將float轉換為char數組的代碼:
///<summary>Append a float without memory allocation.</summary>
public StringFast Append( float valueF )
{
double value = valueF;
m_isStringGenerated = false;
ReallocateIFN( 32 ); // Check we have enough buffer allocated to handle any float number
// Handle the 0 case
if( value == 0 )
{
m_buffer[ m_bufferPos++ ] = '0';
return this;
}
// Handle the negative case
if( value < 0 )
{
value = -value;
m_buffer[ m_bufferPos++ ] = '-';
}
// Get the 7 meaningful digits as a long
int nbDecimals = 0;
while( value < 1000000 )
{
value *= 10;
nbDecimals++;
}
long valueLong = (long)System.Math.Round( value );
// Parse the number in reverse order
int nbChars = 0;
bool isLeadingZero = true;
while( valueLong != 0 || nbDecimals >= 0 )
{
// We stop removing leading 0 when non-0 or decimal digit
if( valueLong%10 != 0 || nbDecimals <= 0 )
isLeadingZero = false;
// Write the last digit (unless a leading zero)
if( !isLeadingZero )
m_buffer[ m_bufferPos + (nbChars++) ] = (char)('0' + valueLong%10);
// Add the decimal point
if( --nbDecimals == 0 && !isLeadingZero )
m_buffer[ m_bufferPos + (nbChars++) ] = '.';
valueLong /= 10;
}
m_bufferPos += nbChars;
// Reverse the result
for( int i=nbChars/2-1; i>=0; i-- )
{
char c = m_buffer[ m_bufferPos-i-1 ];
m_buffer[ m_bufferPos-i-1 ] = m_buffer[ m_bufferPos-nbChars+i ];
m_buffer[ m_bufferPos-nbChars+i ] = c;
}
return this;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.