Это действительно тот же вопрос, что и реализация этих функций/как они работают внутри. Я просто расскажу о вкладе в этот ответ; Я не уверен, какие алгоритмы хороши для float->string.
Функции, предоставляемые ОС, позволяют читать/записывать (печатать) символы по одному или блоками. Интересная / специфичная для FP часть проблемы - это только часть float- > string и string- > float. Все остальное то же самое, что и для чтения/печати целых чисел (различия по модулю в соглашении о вызовах: числа с плавающей запятой обычно возвращаются в регистрах FP).
Правильная реализация strtod
(строка удваивается) и эквивалент одинарной точности очень нетривиален, если вы хотите, чтобы результат всегда был правильно округлен до ближайшего представимого значения FP, особенно если вы хотите, чтобы он также был эффективным и работал для входных данных вплоть до пределов самых больших конечных значений, которые double
может содержать .
Как только вы узнаете детали алгоритма (с точки зрения просмотра одиночных цифр и выполнения умножения/деления/сложения FP или целочисленных операций с битовым шаблоном FP), вы можете реализовать его на ассемблере для любой платформы, которая вам нравится. По какой-то причине вы использовали инструкцию x87 finit
в своем примере.
См. http://www.exploringbinary.com/how-glibc-strtod-works/ для подробного изучения реализации glibc и http://www.exploringbinary.com/how-strtod-works-and-sometimes-doesnt/ для другой широко используемой реализации.
Излагая первую статью, strtod
glibc использует целочисленные арифметические операции повышенной точности. Он анализирует входную десятичную строку, чтобы определить целую часть и дробную часть. например 456.833e2
(научная запись) имеет целую часть 45683
и дробную часть 0.3
.
Он преобразует обе части в числа с плавающей запятой по отдельности. Целочисленная часть проста, потому что уже есть аппаратная поддержка преобразования целых чисел в числа с плавающей запятой. например x87 fild
или SSE2 cvtsi2sd
или что-то еще на других архитектурах. Но если целая часть больше, чем максимальное 64-битное целое, это не так просто, и вам нужно преобразовать BigInteger в float/double, что аппаратно не поддерживает.
Обратите внимание, что даже FLT_MAX
(одинарная точность) для IEEE binary32 float
равно (2 − 2^−23) × 2^127
, что просто немного ниже 2^128, поэтому вы можете использовать 128-битное целое число для строки->float
, и если это перенос, то правильным результатом float
будет +Infinity
. FLT_MAX
битовый шаблон равен 0x7f7fffff
: мантисса все единицы = 1,999... с максимальным показателем. В десятичном виде это ~3.4 × 10^38
.
Но если вы не заботитесь об эффективности, я думаю, вы могли бы преобразовать каждую цифру в float
(или проиндексировать массив уже преобразованных значений float
) и сделать обычное total = total*10 + digit
или, в данном случае, total = total*10.0 + digit_values[digit]
. FP mul/add является точным для целых чисел до точки, где два соседних представляемых значения находятся дальше друг от друга, чем 1,0 (т. е. когда nextafter(total, +Infinity)
равно total+2.0
), т. е. когда 1 ulp больше, чем 1.0
.
На самом деле, чтобы получить правильное округление, вам нужно добавить маленькие значения сначала, иначе каждое из них округляется в меньшую сторону, тогда как все вместе они могли бы увеличить большое значение до следующего представимого значения.
Таким образом, вы, вероятно, можете использовать FPU для этого, если вы делаете это осторожно, например, работаете с фрагментами из 8 цифр и масштабируете на 10 ^ 8 или что-то в этом роде и добавляете, начиная с наименьшего. Вы можете преобразовать каждую строку из 8 цифр в целое число и использовать аппаратное обеспечение int
->float
.
Дробная часть еще сложнее, особенно если вы хотите избежать повторного деления на 10, чтобы получить разрядные значения, чего вам следует избегать, потому что это медленно и потому что 1/10
не может быть точно представлено в двоичном формате с плавающей запятой, поэтому все ваши значения мест будут иметь ошибку округления, если вы сделаете это «очевидным» способом.
Но если целая часть очень велика, все 53 бита мантиссы double
уже могут быть определены целой частью. Таким образом, glibc проверяет и выполняет деление только на большие целые числа, чтобы получить необходимое количество битов (если они есть) из дробной части.
В любом случае, я настоятельно рекомендую прочитать обе статьи.
Кстати, см. https://en.wikipedia.org/wiki/Double-precision_format_floating-point если вы не знакомы с битовыми шаблонами, которые IEEE754 binary64, также известный как double
, использует для представления чисел. Вам не нужно быть нужным, чтобы написать упрощенную реализацию, но это поможет понять float. А с x86 SSE вам нужно знать, где находится бит знака, чтобы реализовать абсолютное значение (ANDPS) или отрицание (XORPS). Самый быстрый способ вычисления абсолютного значения с использованием SSE . Для abs
или neg
нет специальных инструкций, вы просто используете логические операции для управления битом знака. (Гораздо эффективнее, чем вычитание из нуля.)
Если вас не волнует точность последнего ULP (единица измерения на последнем месте = младший бит мантиссы), вы можете выполнить более простой алгоритм умножения на 10 и добавления, как для строки -> целого числа, а затем масштабировать в степени 10 в конце.
Но надежная библиотечная функция не может этого сделать, поскольку создание временного значения, во много раз превышающего окончательный результат, означает, что оно будет переполняться (до +/- Infinity
) для некоторых входных данных, которые находятся в пределах диапазона, который может представлять double
. Или, возможно, переполнение до +/- 0.0
, если вы создаете меньшие временные значения.
Раздельная обработка целой и дробной части позволяет избежать проблемы переполнения.
См. эту реализацию C на codereview.SE для примера очень простого подхода умножения/сложения, который скорее всего переполнится. Я только быстро просмотрел его, но я не вижу, чтобы он разделял целую/дробную часть. Он обрабатывает только научную нотацию E99
или что-то еще в конце, с повторным умножением или делением на 10.
person
Peter Cordes
schedule
28.11.2017
float
илиdouble
может не соответствовать 64-битному целому числу, поэтому вам потенциально может потребоваться несколько шагов для обработки самых больших значений FP, даже без учета дробной части. (И работать с наибольшей цифрой сначала небезопасно из-за округления.) - person Peter Cordes   schedule 29.11.2017