WedX - журнал о программировании и компьютерных науках

Как преобразовать double в строку, используя только math.h?

Я пытаюсь преобразовать двойное число в строку в собственном приложении NT, то есть в приложении, которое зависит только от ntdll.dll. К сожалению, версия vsnprintf ntdll не поддерживает %f и др., что вынуждает меня выполнять преобразование самостоятельно.

Вышеупомянутая ntdll.dll экспортирует лишь некоторые из функций math.h (floor, ceil, log, pow, ...). Однако я достаточно уверен, что при необходимости смогу реализовать любую из недоступных math.h функций.

В libc GNU есть реализация преобразования с плавающей запятой, но код чрезвычайно плотный и трудный для понимания (стиль отступа GNU здесь не помогает).

Я уже реализовал преобразование, нормализовав число (то есть умножив/делив число на 10, пока оно не окажется в интервале [1, 10)), а затем сгенерировал каждую цифру, отрезав целую часть с помощью modf и умножив дробную часть на 10. Это работает. , но есть потеря точности (правильные только первые 15 цифр). Потеря точности, конечно, присуща алгоритму.

Я бы согласился с 17 цифрами, но алгоритм, который мог бы правильно генерировать произвольное количество цифр, был бы предпочтительнее.

Не могли бы вы предложить алгоритм или указать мне хороший ресурс?

16.09.2009

  • стиль отступа GNU здесь не помогает: используйте отступ ( indent.isidore-it.eu/beautify. html ), есть версия для Windows ( gnuwin32.sourceforge.net/packages/indent .htm ), который я не тестировал. 16.09.2009
  • Спасибо, pmg, отступ, к сожалению, не единственный недостаток рассматриваемого кода. 16.09.2009
  • Я написал статью о неточном методе преобразования, который вы описали, — на случай, если год спустя вам все еще будет интересно: exploringbinary.com/ 12.11.2010

Ответы:


1

Числа с двойной точностью не имеют более 15 значащих (десятичных) цифр точности. Вы абсолютно никак не можете получить «произвольное количество цифр правильно»; double не большие числа.

Поскольку вы говорите, что вас устраивает 17 значащих цифр, используйте long double; в Windows, я думаю, это даст вам 19 значащих цифр.

16.09.2009
  • Крис, спасибо за ответ. Чтобы скруглить двойное › строковое › двойное, вам определенно нужно 17 цифр (см. Golberg для доказательства: docs.sun.com/source/806-3568/ncg_goldberg.html#1251). Более того, двойное число представляет собой уникальное рациональное число, для точного представления которого может потребоваться бесконечное количество десятичных цифр. 16.09.2009
  • Вы не можете использовать двойные числа для точного представления рациональных чисел. Вместо этого вам нужно иметь два целых числа (возможно, длинных) для независимого представления числителя и знаменателя. 16.09.2009
  • Что касается кругового обхода, вы должны сериализовать свой двойник в двоичной форме, а не в десятичной. 16.09.2009
  • Крис, я не пытаюсь представить рациональное число двойным числом. Вместо этого я имею двойное число, представляющее некоторое рациональное число с, возможно, бесконечным десятичным представлением (поэтому разумно требовать от преобразования произвольное количество цифр). 16.09.2009
  • Двойной не имеет произвольную точность. Вы теряете точность, потому что двойник не может ее представить. 16.09.2009
  • Grayfade, я никогда не говорил, что двойники имеют произвольную точность. Они, конечно, ограничены 52 двоичными разрядами (на самом деле 53). Однако, когда вы преобразуете это двоичное число (содержащее не более 53 цифр) в десятичное, вы можете получить бесконечное количество десятичных цифр. 16.09.2009
  • Нет, вы не получите бесконечное количество десятичных цифр. Почему? Потому что никакое отношение, где знаменатель является степенью двойки, никогда не дает повторяющегося десятичного представления. 16.09.2009
  • @Avakar: кажется, вам нужен вопрос о том, почему я не получаю бесконечное количество десятичных цифр с представлением с плавающей запятой? :) 16.09.2009
  • О... мой плохой, ты прав :) Сотрите слово бесконечность. Однако количество десятичных цифр может быть довольно большим. 16.09.2009
  • xtofl, ха-ха, конечно, это была довольно глупая ошибка. :) Однако исходный вопрос остается в силе. 16.09.2009
  • Оно может быть высоким, но если все, что вас интересует, это круговой обмен, напечатайте свои цифры в шестнадцатеричном формате. Серьезно, таким образом вы потеряете намного меньше точности. 16.09.2009
  • Крис, меня не волнуют только поездки туда и обратно. Я пытаюсь подражать поведению printf, который в msvc действительно дает 17 цифр, которые можно использовать для кругового обхода. gcc может дать произвольное количество цифр. 16.09.2009
  • Если вы действительно эмулируете printf, вы обычно не получите столько цифр. Я не знаю, стандартизировано ли количество цифр по умолчанию или нет, но обычное использование будет печатать только некоторые из них (и действительно мало, что имеет значение) 16.09.2009
  • dribeas, точность по умолчанию для %f установлена ​​на 6, но вы можете установить точность вручную: printf("%.17f", 1.1);. 16.09.2009

  • 2

    Я думал об этом немного больше. Вы теряете точность, потому что вы нормализуете, умножая на некоторую степень 10 (вы выбрали [1,10), а не [0,1), но это незначительная деталь). Если вы сделаете это со степенью двойки, вы не потеряете точности, но тогда вы получите «десятичные цифры» * 2 ^ e; вы могли бы реализовать двоично-десятичную арифметику и вычислить произведение самостоятельно, но это не звучит весело.

    Я почти уверен, что вы можете разделить двойное g=m*2^e на две части: h=floor(g*10^k) и i=modf(g*10^k) для некоторого k, а затем отдельно преобразовать в десятичные цифры, а затем сшить их вместе, но как насчет более простого подхода: используйте "long double" (80 бит, но я слышал, что Visual C++ может не поддерживать это?) с вашим текущим подходом и останавливаться после 17 цифр.

    _gcvt должен сделать это (редактировать - это не в ntdll.dll, в какой-то msvcrt*.dll?)

    Что касается точности десятичных цифр, IEEE binary64 содержит 52 двоичных цифры. 52 * log10 (2) = 15,65... (отредактируйте: как вы указали, для поездки туда и обратно вам нужно более 16 цифр)

    16.09.2009
  • Спасибо, вранг-вранг. К сожалению, у меня нет _gcvt в нативном формате. Что касается значения 15,65, это означает только то, что вы можете представить число с 15 цифрами точно в двойном размере. Однако для кругового обхода вам нужно 17 цифр. 16.09.2009
  • Я согласен с тем, что для точного представления d-битной дроби на [0,1) требуется более log10 (2) * d десятичных цифр, например. 1/16 = (0,1111)_2 = 0,9375, что соответствует 4 десятичным цифрам для 4 двоичных цифр. 16.09.2009
  • Да, я полагаю, что большая точность даст мне эти 17 цифр. К сожалению, long double эквивалентно double в msvc (по крайней мере, в msvc9). 16.09.2009
  • Что касается преобразования целых и дробных частей по отдельности: помните, что удвоения могут достигать 1.e308. Я потеряю 17-ю цифру, как только выполню первое умножение не на степень двойки. 16.09.2009

  • 3

    После долгих поисков я нашел документ под названием Печать чисел с плавающей запятой. Числа быстро и точно. Он использует точную рациональную арифметику, чтобы избежать потери точности. Он цитирует более раннюю статью: Как точно печатать числа с плавающей запятой, который, однако, кажется, требует подписки ACM для доступа.

    Поскольку предыдущая статья была переиздана в 2006 году, я склонен полагать, что она все еще актуальна. Точная рациональная арифметика (которая требует динамического распределения) кажется неизбежным злом.

    25.09.2009
  • Ссылка не работает для первого URL. Вместо этого используйте это: citeseerx.ist.psu. образование/viewdoc/ 07.02.2014
  • А вот полная реализация этого алгоритма в коде C на SourceForge: code.google.com/p/double-conversion/downloads/ 07.02.2014

  • 4

    Полная реализация кода C для самого быстрого из известных (на сегодняшний день) алгоритмов: https://code.google.com/p/double-conversion/downloads/list

    Он даже включает набор тестов.

    Это код C, лежащий в основе алгоритма, описанного в этом PDF-файле: Быстрая и точная печать чисел с плавающей запятой https://www.cs.indiana.edu/~burger/FP-Printing-PLDI96.pdf

    06.02.2014

    5

    Поддерживает ли vsnprintf I64?

    double x = SOME_VAL; // allowed to be from -1.e18 to 1.e18
    bool sign = (SOME_VAL < 0);
    if ( sign ) x = -x;
    __int64 i = static_cast<__int64>( x );
    double xm = x - static_cast<double>( i );
    __int64 w = static_cast<__int64>( xm*pow(10.0, DIGITS_VAL) ); // DIGITS_VAL indicates how many digits after the decimal point you want to get
    
    char out[100];
    vsnprintf( out, sizeof out, "%s%I64.%I64", (sign?"-":""), i, w );
    

    Другой вариант — попытаться найти реализацию gcvt.

    16.09.2009
  • Это довольно быстро и просто, за исключением того, что это не работает, когда показатель степени большой или маленький, или число отрицательное. 16.09.2009
  • Кирилл, спасибо. К сожалению, мне нужно поддерживать номера до и выше 1.e308 :) 16.09.2009
  • @avakar, тогда мой ответ тебе не подходит :) 16.09.2009

  • 6

    Вы смотрели на реализацию uClibc printf?

    17.09.2009
  • Спасибо за подсказку, кафе. Я изучил это, и кажется, что они используют тот же алгоритм, что и я, хотя на каждом этапе они извлекают более одной цифры. Я склонен полагать, что они теряют точность так же, как и я. 17.09.2009

  • 7
  • Эти две строки никогда не будут выполнены. Они взаимоисключающие с внешней проверкой if. Ошибка? if (-1 != nDigitsF) nDigitsF= max(nDigitsF, nDigitsI+nDigitsF-(bNeg?2:1)-4); 06.02.2014
  • Кроме того, что такое afPower10? 07.02.2014
  • @srm: Почему эти строки взаимоисключающие? внешнее if относится к nDigitsI, а внутреннее if — к nDigitsF. 07.02.2014
  • виноват. Я перепутал I и F, когда читал код. Я знаю, что это увеличило бы текст, но имена переменных, которые почти идентичны, за исключением одной буквы, очень трудно понять. Мне стало легче следовать некоторой вашей логике, когда я заменил текст Integral на I и Fraction на F. Это сохраняет одинаковое качество длины имен переменных и обеспечивает большую защиту от неправильного чтения I как F или наоборот. Я понимаю, что знатоки английского языка будут съеживаться из-за того, что я использовал форму прилагательного для I и форму существительного для F. :-) 21.04.2014
  • Новые материалы

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..

    Учебные заметки: создание моего первого пакета Node.js
    Это мои обучающие заметки, когда я научился создавать свой самый первый пакет Node.js, распространяемый через npm. Оглавление Глоссарий I. Новый пакет 1.1 советы по инициализации..

    Забудьте о Matplotlib: улучшите визуализацию данных с помощью умопомрачительных функций Seaborn!
    Примечание. Эта запись в блоге предполагает базовое знакомство с Python и концепциями анализа данных. Привет, энтузиасты данных! Добро пожаловать в мой блог, где я расскажу о невероятных..

    ИИ в аэрокосмической отрасли
    Каждый полет – это шаг вперед к великой мечте. Чтобы это происходило в их собственном темпе, необходима команда астронавтов для погони за космосом и команда технического обслуживания..


    Для любых предложений по сайту: [email protected]