2016-12-20 2 views
1

Вы знаете, как преобразовать float в буфер символов, не выделяя никакой памяти?Преобразование float в char [] без выделения памяти

=> Я просто хочу повторить то же самое, что и float.ToString() do; так что я могу поместить результат в буфер вместо выделения строки

Я написал функцию, но она не работает очень хорошо «округление»:

  • 39,71 становится «39,709996»
  • 39.71001 «39.710004»

Это потому, что 39.71 в качестве поплавка представляет собой округление сохраненного значения в памяти, которое составляет 39.709996. С некоторым округлением в моей функции я могу легко прийти к чему-то вроде этого:

  • 39,71 становится «39,71»
  • 39,71001 становится «39,71»

который не большой либо, как хотелось бы чтобы сохранить точный алгоритм float.ToString(), которому удается написать «39.71» и «39.71001»

Знаете ли вы, как это работает float.ToString()?

Прецизионность по моей цели: я хочу добавить большое количество поплавка (смешанного с другими типами) в очень большой строке - и выделить эту строку только один раз в конце, чтобы избежать слишком большого сбора мусора. Поэтому мне действительно нужно преобразовать float в массив char (какой бы то ни было формат шрифта, просто нет строки unmutable)

+1

его невозможно хранить, не выделяя никакой памяти. –

+0

У вас должно быть какое-то распределение, так как вам нужно выделить хотя бы строку, которую вы будете возвращать из функции – thecoop

+0

Я не хочу, чтобы получить уже созданную функцию, но чтобы понять, как поплавок.Алгоритм Tostring() работает и делает из него пользовательскую функцию. – Gaddy

ответ

1

Из ваших комментариев кажется, что вы хотите иметь возможность форматировать большое количество поплавков (и другое значение типы) в очень большой массив символов, без необходимости делать много распределений памяти.

Невозможно избежать всех распределений памяти, но вы можете имитировать их с помощью StringBuilder.AppendFormat() и StringBuilder.CopyTo().

Если у вас есть представление о максимальной длине конечного массива символов, вы можете инициализировать 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 и один для полукокса [] массива.

Тем не менее, вполне вероятно, что за кулисами произойдет большое количество других небольших распределений, но из-за того, как работает GC, они вряд ли попадут в коллекцию 1 поколения, поэтому маловероятно чтобы вызвать проблему с производительностью.

+0

Большое спасибо за ваш ответ! Но stringbuilder на самом деле значительно выделяет память (по крайней мере, в версии C#, используемой компилятором Unity Mono). гораздо меньше, чем некоторые базовые приложения, но все же слишком много, когда вы действительно хотите avoi d памяти. И да, в некоторых (единственных) играх, которые нуждаются в большом количестве выступлений, эти распределения на самом деле имеют значение (и вы можете легко увидеть, где они находятся с профилировщиком). – Gaddy

+0

Точнее в вашем примере: - 83.7kb выделено в цикл (с 811 итерациями). - 52kb, если я заменил ваш .AppendFormat просто .Append (x) - 0, если я его заменил Append ("blabla"). - 6.2Mb, если я использую строку с str + = "," + x; Итак, StringBuilder очень хорош, но далек от совершенства, к сожалению, – Gaddy

+0

@ Gaddy Хорошо, что вы не получите «идеального» (без написания собственного форматирования чисел с плавающей запятой), но вам просто нужно «достаточно хорошо» ... Помните, GC довольно дешев для объектов, которые не делают его прошлым поколением 0. –

2

Вы можете позвонить в C stdlib sprintf() и друзьям, которым необходимо решить большинство ваших проблем с форматированием и данными. См., Например, https://stackoverflow.com/a/2479210/3150802. Отслеживание положения печати, т.е.индекс в массив символов, путем оценки возвращаемого значения sprintf() будет иметь важное значение.

Существует определенное ограничение производительности при пересечении управляемой/неуправляемой границы, которая неизбежна. Возможной стратегией смягчения является уменьшение числа таких пересечений, например, путем записи функции обертки C, которая получает большой массив поплавков и записывает их все за один раз, используя s*printf().

Потенциальное наказание за маршалинг данных (т. Е. Обратное преобразование между CLI и собственными представлениями) может не быть проблемой для POD, таких как поплавки и адреса, если они по-разному идентичны в обоих мирах.

+0

Очень хорошая идея! Я не уверен в производительности, не так ли потребуется, чтобы сделать вызовы dll C с C#? Определенно стоит проверить! – Gaddy

+0

Я почти уверен, что накладные расходы на это уменьшат сбережения ... Привязка массива char не из дешевых. –

+0

Стоит проверить, хотя! –

1

Ниже приведено решение, которое я, наконец, написал, благодаря исходному коду 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. Конечно, за последние два я не воссоздаю их каждый раз.

Ниже приведены результаты, с выделением памяти и времени:

  • Строка (+): 302.7KB, 1.75ms
  • строка (.concat): 302.7KB, 1.85ms
  • StringBuilder: 259.8KB, 1.81ms
  • StringFast: 58.6KB, 1.68ms

И если я заменю свои 5 операций по сцеплению 100 поплавка:

  • строка (+): 36.8MB, 116.19ms
  • строку (.concat): 36.8MB, 116.19ms
  • StringBuilder: 6.0MB, 63.26ms
  • StringFast: 0.6Mb, 51.09ms

Как вы можете видеть, StringBuilder не так уж хорош, особенно если в строке есть всего несколько операций. Распределение класса StringFast только вызвано окончательной ToString() я (для того, чтобы иметь возможность использовать строку с другими функциями)

Вот код для преобразования поплавка в массив символов:

///<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; 
} 
Смежные вопросы