dtoa vs sprintf_s vs алгоритм Grisu3 - PullRequest
2 голосов
/ 06 июля 2011

Каков наилучший способ рендеринга чисел двойной точности в виде строк в C ++?

Я наткнулся на эту статью http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/, в которой рассказывается о печати чисел с плавающей запятой?

Я былиспользуя sprintf_s.Я не понимаю, почему мне нужно изменить код?

Ответы [ 5 ]

3 голосов
/ 05 ноября 2011

Если вы довольны sprintf_s, вам не следует меняться.Однако, если вам нужно отформатировать вывод таким образом, который не поддерживается вашей библиотекой, вам может потребоваться переопределить специализированную версию sprintf (с любым из известных алгоритмов).

Например, JavaScript имеет очень точныйтребования к тому, как должны быть напечатаны его номера (см. раздел 9.8.1 спецификации ).Правильный вывод не может быть достигнут простым вызовом sprintf.Действительно, Grisu был разработан для реализации правильной печати чисел для компилятора JavaScript.

Grisu также быстрее, чем sprintf, но если печать с плавающей точкой не является узким местом в вашем приложении, это не должно быть причиной для переключенияв другую библиотеку.

3 голосов
/ 09 августа 2011

Ахах!

Проблема, изложенная в статье, которую вы даете, состоит в том, что для некоторых чисел компьютер отображает что-то теоретически правильное, но не то, что мы, люди, использовали бы.

Например, как говорится в статье, 1,2999999 ... = 1,3, поэтому, если ваш результат равен 1,3, для компьютера (вполне) правильно отобразить его как 1,299999999 ... Но это не то, что вы видели. ..

Теперь вопрос в том, почему компьютер делает это? Причина в том, что компьютер вычисляет в базе 2 (двоичный), и что мы обычно вычисляем в базе 10 (десятичный). Результаты одинаковы (слава богу!), Но внутреннее хранилище и представление не совпадают.

Некоторые числа выглядят хорошо при отображении в базе 10, например, как 1.3, но другие нет, например, 1/3 = 0.333333333 .... То же самое в базе 2, некоторые числа "хорошо" выглядят в базе 2 (как правило, когда состоит из фракций 2), а другие нет. Когда компьютер хранит число внутри себя, он может не иметь возможности сохранить его «точно» и сохранить максимально близкое представление, даже если число выглядело «конечным» в десятичном виде. Так что да, в этом случае это немного "дрейфует". Если вы делаете это снова и снова, вы можете потерять точность. Но другого пути нет (если только не используются специальные математические библиотеки, способные хранить дроби)

Проблема возникает, когда компьютер пытается вернуть вам в базе 10 номер, который вы ему дали. Тогда компьютер может дать вам 1,299999 вместо 1,3, который вы ожидали.

Это также причина, по которой вы должны никогда сравнивать числа с плавающей точкой с ==, <,>, но вместо этого использовать специальные функции islessgreater (a, b) isgreater (a, b) и т. д.

То есть фактическая функция, которую вы используете (sprintf), хороша и настолько точна, насколько это возможно, она дает вам правильные значения, вы просто должны знать, что при работе с числами с плавающей запятой 1.2999999 с максимальной точностью - это нормально, если вы ожидали 1,3

Теперь, если вы хотите «красиво напечатать» эти числа, чтобы получить наилучшее «человеческое» представление (база 10), вы можете использовать специальную библиотеку, например, grisu3, которая попытается отменить дрейф, который мог произойти и выровняйте число по ближайшему представлению базы 10.

Теперь библиотека не может использовать хрустальный шар и находить, какие числа были смещены или нет, поэтому может случиться, что вы действительно имели в виду 1.2999999 с максимальной точностью, как хранится в компьютере, и библиотека будет "конвертировать" до 1,3 ... Но это не хуже и не менее точно, чем отображение 1,29999 вместо 1,3.

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

Надеюсь, это поможет!

1 голос
/ 07 июля 2011

Лучший способ сделать это на любом разумном языке:

  1. Используйте библиотеку времени выполнения вашего языка. Никогда не катай свои собственные. Даже если у вас есть знания и любопытство, чтобы написать это, вы не хотите проверять это и не хотите поддерживать это.
  2. Если вы заметили какое-либо неправильное поведение при преобразовании библиотеки времени выполнения, сообщите об ошибке .
  3. Если эти преобразования являются измеримым узким местом для вашей программы, не пытайтесь сделать их быстрее. Вместо этого найдите способ избежать их вообще. Вместо того, чтобы хранить числа в виде строк, просто сохраняйте данные с плавающей точкой (после возможного контроля порядка байтов). Если вам нужно строковое представление, используйте вместо этого шестнадцатеричный формат с плавающей точкой.

Я не хочу отговаривать вас или кого-либо еще. Это действительно захватывающие функции для работы, но они также шокирующие и сложные, и попытка создать хорошее тестовое покрытие для любой не наивной реализации является еще более сложной задачей. Не начинайте, если не готовы потратить месяцы на размышления о проблеме.

1 голос
/ 06 июля 2011

В C ++ почему вы не используете iostreams?Вероятно, вам следует использовать cout для консоли и ostringstream для строкового ориентированного вывода (если только у вас нет особой необходимости использовать метод семейства printf).производительность форматирования, если фактическое профилирование не показывает, что процессор является узким местом (по сравнению, скажем, с вводом / выводом).

0 голосов
/ 07 июля 2011
void outputdouble( ostringstream & oss, double d )
{
    oss.precision( 5 );
    oss << d;
}

http://www.cplusplus.com/reference/iostream/ostringstream/

...