Как Вы печатаете ТОЧНОЕ значение числа с плавающей точкой?

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

Тем не менее каждое возможное значение с плавающей точкой соответствует точно двухместному рациональному (рациональное число p/q где q питание 2), которое в свою очередь имеет точное десятичное представление.

Мой вопрос: Как Вы находите это точное десятичное представление эффективно? sprintf и подобные функции обычно только указываются до многих значащих цифр для уникального определения исходного значения с плавающей точкой; они не обязательно печатают точное десятичное представление. Я знаю один алгоритм, который я использовал, но это очень медленно, O(e^2) где e экспонента. Вот схема:

  1. Преобразуйте мантиссу в десятичное целое число. Можно или сделать это путем разделения битов для чтения мантиссы непосредственно, или можно записать грязный цикл с плавающей точкой, который сначала умножает значение на питание два для помещения ее в диапазон 1 <=x <10, затем осуществляет цифру за один раз путем кастинга к интервалу, вычитанию и умножению на 10.
  2. Примените экспоненту путем повторного умножения или деления на 2. Это - операция на строке десятичных цифр, которые Вы генерировали. Каждые ~3 умножения добавит дополнительную цифру налево. Каждый dividion добавит дополнительную цифру направо.

Это - действительно самое лучшее? Я сомневаюсь относительно этого, но я не эксперт с плавающей точкой, и я не могу найти способ сделать основу 10 вычислений на представлении с плавающей точкой числа, не сталкиваясь с возможностью неточных результатов (умножение, или деление на что-либо кроме питания 2 является операцией с потерями на числах с плавающей точкой, если Вы не знаете, что у Вас есть свободные биты для работы с).

32
задан R.. 9 July 2010 в 17:50
поделиться

9 ответов

В этом вопросе есть бюрократическая часть и алгоритмическая часть. Число с плавающей запятой хранится внутри как (2 e × m), где e - показатель степени (в двоичном формате), а m - мантисса. Бюрократическая часть вопроса заключается в том, как получить доступ к этим данным, но Р., похоже, больше интересует алгоритмическая часть вопроса, а именно преобразование (2 e × m) в дробь (a / b) в десятичная форма. Ответ на бюрократический вопрос на нескольких языках - «frexp» (это интересная деталь, о которой я не знал до сегодняшнего дня).

Это правда, что на первый взгляд требуется O (e 2 ) работы, чтобы просто записать 2 e в десятичной системе, и еще больше времени для мантиссы. Но, благодаря магии алгоритма быстрого умножения Шёнхаге-Штрассена , вы можете сделать это за Õ (e) времени, где тильда означает «с точностью до логарифмических множителей». Если вы считаете Шёнхаге-Штрассен волшебством, то не так уж и сложно придумать, что делать. Если e четно, вы можете рекурсивно вычислить 2 e / 2 , а затем возвести его в квадрат, используя быстрое умножение. С другой стороны, если e нечетно, вы можете рекурсивно вычислить 2 e-1 , а затем удвоить его. Вы должны быть осторожны, чтобы проверить, есть ли версия Шёнхаге-Штрассена в базе 10. Хотя это не так широко документировано, это можно сделать в любой базе.

Преобразование очень длинной мантиссы из двоичной в основание 10 - это не совсем тот же вопрос, но на него есть похожий ответ. Вы можете разделить мантиссу на две половины: m = a 2 k + b.Затем рекурсивно преобразуйте a и b в базу 10, преобразуйте 2 ^ k в базу 10 и выполните еще одно быстрое умножение для вычисления m в базе 10.

Абстрактный результат всего этого заключается в том, что вы можете преобразовать целые числа из одной базы в другой через Õ (N) раз.

Если речь идет о стандартных 64-битных числах с плавающей запятой, то это слишком мало для причудливого алгоритма Шёнхаге-Штрассена. В этом диапазоне вы можете вместо этого сохранить работу с помощью различных уловок. Один из подходов состоит в том, чтобы сохранить все 2048 значений 2 e в таблице поиска, а затем работать с мантиссой с асимметричным умножением (между длинным и коротким умножением). Другой трюк - работать с базой 10000 (или с более высокой степенью 10, в зависимости от архитектуры) вместо базы 10. Но, как указывает R. в комментариях, 128-битные числа с плавающей запятой уже позволяют вызывать достаточно большие экспоненты для вызова. подвергают сомнению обе таблицы поиска и стандартное длинное умножение. На практике длинное умножение является самым быстрым до нескольких цифр, затем в значительном среднем диапазоне можно использовать умножение Карацубы или умножение Тоома-Кука , а затем после этого вариант Шёнхаге-Штрассена лучше всего не только в теории, но и на практике.

На самом деле, в большом целочисленном пакете GMP уже есть Õ (N) преобразование счисления счисления по времени, а также хорошая эвристика для выбора алгоритма умножения. Единственная разница между их решением и моим состоит в том, что вместо того, чтобы выполнять какие-либо большие арифметические операции с основанием 10, они вычисляют большие степени 10 по основанию 2.В этом решении они также нуждаются в быстром делении, но его можно получить путем быстрого умножения любым из нескольких способов.

35
ответ дан 27 November 2019 в 20:35
поделиться

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

5
ответ дан 27 November 2019 в 20:35
поделиться

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

0
ответ дан 27 November 2019 в 20:35
поделиться

Совершенно верно, почему бы сначала не разбить показатель степени на сумму двоичных показателей, а затем все ваши операции будут без потерь.

Т.е.

10^2 = 2^6 + 2^5 + 2^2

Затем сумма:

mantissa<<6 + mantissa<<5 + mantissa<<2

Я думаю, что разбивка будет на O (n) по количеству цифр, сдвиг - O (1), а суммирование - O (n) цифр. ..

У вас должен быть достаточно большой целочисленный класс, чтобы хранить результаты, конечно ...

Дайте мне знать - мне любопытно, это действительно заставило меня задуматься. : -)

0
ответ дан 27 November 2019 в 20:35
поделиться

Хотя это C# и ваш вопрос помечен тегом C, у Jon Skeet есть код для преобразования double в его точное представление в виде строки: http://www.yoda.arachsys.com/csharp/DoubleConverter.cs

С первого взгляда кажется, что это не слишком сложно перенести на C, и даже проще написать на C++.

При дальнейшем размышлении выясняется, что алгоритм Джона также является O(e^2), поскольку он также перебирает экспоненту. Однако это означает, что алгоритм является O(log(n)^2) (где n - число с плавающей точкой), и я не уверен, что вы сможете преобразовать число из основания 2 в основание 10 за время, превышающее логарифмический квадрат.

3
ответ дан 27 November 2019 в 20:35
поделиться

Нет. Самое близкое к этому - сбросить байты.

-2
ответ дан 27 November 2019 в 20:35
поделиться

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

GNU MPFR - хороший вариант.

Библиотека MPFR - это библиотека C для с плавающей точкой с множественной точностью вычисления с правильным округлением. Основная цель MPFR - обеспечить библиотека для множественной точности вычисление с плавающей запятой, которое оба эффективны и имеют четко определенный семантика.

2
ответ дан 27 November 2019 в 20:35
поделиться

sprintf и подобные функции обычно задаются только до ряда значащих цифр, чтобы однозначно определить исходное значение с плавающей точкой значение; они не обязательно выводят точное десятичное представление.

Вы можете запросить больше значащих цифр, чем указано по умолчанию:

printf("%.100g\n", 0.1);

печатает 0.1000000000000000055511151231257827021181583404541015625

1
ответ дан 27 November 2019 в 20:35
поделиться

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

  1. Функция dtoa() Дэвида Гэя в dtoa.c (http://www.netlib.org/fp/dtoa.c).

  2. Функция ___printf_fp() в файле /stdio-common/printf_fp.c в glibc (http://ftp.gnu.org/gnu/glibc/glibc-2.11.2.tar.gz, например).

Оба выведут столько цифр, сколько вы попросите в %f типа printf (о чем я писал здесь: http://www.exploringbinary.com/print-precision-of-dyadic-fractions-varies-by-language/ и http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too/).

16
ответ дан 27 November 2019 в 20:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: