Я имею в виду, например, что у меня есть следующее число, закодированное в IEEE-754 одинарной точности:
"0100 0001 1011 1110 1100 1100 1100 1100" (approximately 23.85 in decimal)
Двоичное число выше хранится в буквальной строке.
Вопрос в том, как я могу преобразовать эту строку в представление двойной точности IEEE-754 (что-то вроде следующего, но значение не совпадает), БЕЗ потери точности?
"0100 0000 0011 0111 1101 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1010"
который тот же номер закодирован в IEEE-754 двойной точности.
Я попытался использовать следующий алгоритм для преобразования первой строки обратно в десятичное число первым, но он теряет точность.
num in decimal = (sign) * (1 + frac * 2^(-23)) * 2^(exp - 127)
Я использую Qt C ++ Framework на платформе Windows.
РЕДАКТИРОВАТЬ: я должен извиниться, может быть, я не получил четко выраженный вопрос.
Я имею в виду, что я не знаю истинного значения 23,85, я получил только первую строку и хочу преобразовать ее в представление с двойной точностью без потери точности.
Хорошо: оставьте знаковый бит, переписайте показатель степени (минус старое смещение, плюс новое смещение) и дополните мантиссу нулями справа …
(Как говорит @Mark, некоторые особые случаи нужно рассматривать отдельно, а именно, когда смещенный показатель равен нулю или максимуму.)
IEEE-754 (и с плавающей точкой в целом) не может представлять периодические двоичные десятичные дроби с полной точностью. Даже тогда, когда они на самом деле являются рациональными числами с относительно небольшим целым числом и знаменателем. Некоторые языки предоставляют рациональный тип, который может это делать (это языки, которые также поддерживают неограниченные целые числа точности).
Как следствие, эти два номера, которые вы разместили, НЕ являются тем же номером.
Они на самом деле являются:
10111.11011001100110011000000000000000000000000000000000000000 …
10111.11011001100110011001100110011001100110011001101000000000 …
где ...
представляют собой бесконечную последовательность 0
s.
Стивен Кэнон в комментарии выше дает вам соответствующие десятичные значения (не проверял их, но у меня нет оснований сомневаться, что он понял их правильно).
Поэтому преобразование, которое вы хотите сделать, не может быть выполнено, так как число с одинарной точностью не имеет необходимой вам информации (у вас НЕТ СПОСОБА узнать, является ли число на самом деле периодическим или просто выглядит так, как будто оно повторяется) ,
Прежде всего, +1 для идентификации входных данных в двоичном виде.
Во-вторых, это число не соответствует 23,85, но немного меньше. Если вы переверните его последнюю двоичную цифру из 0
в 1
число будет по-прежнему не точно равно 23,85, но немного больше. Эти различия не могут быть адекватно отражены в поплавке, но они могут быть приблизительно зафиксированы в двойном размере.
В-третьих, что ты считать Вы проигрываете, называется точностью, а не точностью. Точность числа всегда увеличивается при преобразовании из одинарной точности в двойную точность, в то время как точность никогда не может быть улучшена путем преобразования (ваше неточное число остается неточным, но дополнительная точность делает его более очевидным).
Я рекомендую преобразовать в число с плавающей точкой или округление или добавить очень маленькое значение непосредственно перед отображением (или регистрацией) числа, потому что визуальный вид — это то, что вам нужно действительно потеря при увеличении точности.
Не поддавайтесь искушению округлить сразу после приведения и использовать округленное значение в последующих вычислениях — это особенно рискованно в циклах. Хотя может показаться, что это устраняет проблему в отладчике, накопленные дополнительные неточности могут еще больше исказить конечный результат.
Возможно, проще всего преобразовать строку в фактическое число с плавающей запятой, преобразовать ее в число типа double и преобразовать ее обратно в строку.
Двоичные числа с плавающей запятой, как правило, не могут точно представлять значения десятичной дроби. Преобразование из десятичного дробного значения в двоичную с плавающей запятой (см. «Беллерофон» в «Как точно читать числа с плавающей запятой» Уильяма Д. Клингера) и из двоичной с плавающей запятой обратно в десятичное значение (см. «Dragon4») в книге «Как печатать числа с плавающей запятой точно» Гая Л. Стила-младшего и Джона Л. Уайта) дают ожидаемые результаты, потому что один преобразует десятичное число в ближайшую представимую двоичную плавающую точку, а другой управляет ошибкой, чтобы узнать, какие десятичное значение, из которого оно получено (оба алгоритма улучшены и сделаны более практичными в David Gay’s dtoa.c. Алгоритмы являются основой для восстановления std::numeric_limits<T>::digits10
десятичные цифры (кроме, возможно, конечных нулей) из значения с плавающей запятой, хранящегося в типе T
,
К сожалению, расширение float
к double
разрушает значение: попытка отформатировать новое число во многих случаях не даст десятичного оригинала, потому что float
дополненный нулями отличается от ближайшего double
Bellerophon создаст и, таким образом, ожидает Dragon4. Однако есть два подхода, которые работают достаточно хорошо:
float
в строку, и эта строка в double
, Это не особенно эффективно, но может быть доказано, что оно дает правильные результаты (конечно, при условии правильной реализации не совсем тривиальных алгоритмов).double
и, наконец, разделите полученное двойное число на исходную степень 10. У меня нет доказательств того, что это дает правильное число, но для диапазона значений, которые меня интересуют, и которое я хочу точно сохранить в float
, это работает.Один разумный подход, чтобы избежать этой цели, заключается в использовании десятичное число с плавающей точкой значения, как описано для C ++ в Десятичный TR на первом месте. К сожалению, они пока не являются частью стандарта, но я представил предложение в комитет по стандартизации C ++, чтобы это изменить.