В первую очередь, это не вопрос о новичке с плавающей точкой. Я знаю, что результаты арифметики с плавающей точкой (не говоря уже о трансцендентных функциях) обычно не могут быть представлены точно, и что большинство завершающихся десятичных чисел не может быть представлено точно как двоичные числа с плавающей точкой.
Тем не менее каждое возможное значение с плавающей точкой соответствует точно двухместному рациональному (рациональное число p/q
где q
питание 2), которое в свою очередь имеет точное десятичное представление.
Мой вопрос: Как Вы находите это точное десятичное представление эффективно? sprintf
и подобные функции обычно только указываются до многих значащих цифр для уникального определения исходного значения с плавающей точкой; они не обязательно печатают точное десятичное представление. Я знаю один алгоритм, который я использовал, но это очень медленно, O(e^2)
где e
экспонента. Вот схема:
Это - действительно самое лучшее? Я сомневаюсь относительно этого, но я не эксперт с плавающей точкой, и я не могу найти способ сделать основу 10 вычислений на представлении с плавающей точкой числа, не сталкиваясь с возможностью неточных результатов (умножение, или деление на что-либо кроме питания 2 является операцией с потерями на числах с плавающей точкой, если Вы не знаете, что у Вас есть свободные биты для работы с).
В этом вопросе есть бюрократическая часть и алгоритмическая часть. Число с плавающей запятой хранится внутри как (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.В этом решении они также нуждаются в быстром делении, но его можно получить путем быстрого умножения любым из нескольких способов.
Было проделано много работы по печати чисел с плавающей запятой. Золотым стандартом является распечатка десятичного эквивалента минимальной длины, чтобы при повторном считывании десятичного эквивалента вы получали то же число с плавающей запятой, с которым начинали, независимо от того, какой режим округления был во время обратного чтения. Вы можете прочитать об алгоритме в прекрасной статье Бургера и Дибвига .
Если вам нужны более точные результаты, почему бы вместо этого не использовать математику с фиксированной точкой? Конверсии происходят быстро. Ошибка известна, и ее можно обойти. Не точный ответ на ваш вопрос, но другая идея для вас.
Совершенно верно, почему бы сначала не разбить показатель степени на сумму двоичных показателей, а затем все ваши операции будут без потерь.
Т.е.
10^2 = 2^6 + 2^5 + 2^2
Затем сумма:
mantissa<<6 + mantissa<<5 + mantissa<<2
Я думаю, что разбивка будет на O (n) по количеству цифр, сдвиг - O (1), а суммирование - O (n) цифр. ..
У вас должен быть достаточно большой целочисленный класс, чтобы хранить результаты, конечно ...
Дайте мне знать - мне любопытно, это действительно заставило меня задуматься. : -)
Хотя это 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 за время, превышающее логарифмический квадрат.
Так как я не являюсь экспертом в области вычислений с плавающей запятой, я бы предпочел использовать хорошо протестированную библиотеку с открытым исходным кодом.
GNU MPFR - хороший вариант.
Библиотека MPFR - это библиотека C для с плавающей точкой с множественной точностью вычисления с правильным округлением. Основная цель MPFR - обеспечить библиотека для множественной точности вычисление с плавающей запятой, которое оба эффективны и имеют четко определенный семантика.
sprintf и подобные функции обычно задаются только до ряда значащих цифр, чтобы однозначно определить исходное значение с плавающей точкой значение; они не обязательно выводят точное десятичное представление.
Вы можете запросить больше значащих цифр, чем указано по умолчанию:
printf("%.100g\n", 0.1);
печатает 0.1000000000000000055511151231257827021181583404541015625
Я вижу, что вы уже приняли ответ, но вот пара реализаций этого преобразования с открытым исходным кодом, на которые вы, возможно, захотите взглянуть:
Функция dtoa()
Дэвида Гэя в dtoa.c
(http://www.netlib.org/fp/dtoa.c).
Функция ___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/).