Шестнадцатеричное представление с плавающей точкой в ​​c - PullRequest
0 голосов
/ 03 июня 2018

Когда я читаю шестнадцатеричное обозначение чисел с плавающей точкой в ​​C, я встречаю специальное число "0xa.1fp10" из книги Стивена Прата.Когда я присваивал этот номер переменной с плавающей запятой или переменной double и печатал ее, используя спецификатор формата «% a» в printf, результат был 0x1.43e000p + 13, что не соответствует оригиналу.Но оба имеют одинаковое значение 10364 в десятичном виде.Что здесь происходит?Почему выходное значение изменилось?Как я могу получить исходный номер в качестве вывода?

Ответы [ 3 ]

0 голосов
/ 04 июня 2018

К сожалению, вы не можете получить такой же формат 0xa.1fp10 из printf.Стандарт C определяет, что выходной сигнал %a таков, что для нормального двойного числа, отличного от нуля, будет один ненулевой разряд до . и столько цифр, сколько необходимо для точного представления значения после ..Реализация может выбрать сколько первых бит входит в первую цифру!

Однако в стандарте C11 есть сноска 278 , в которой говорится, что

Двоичные реализации могут выбирать шестнадцатеричную цифру слева от символа десятичной запятой, чтобы последующие цифры выравнивались с полубайтовыми (4-битными) границами.

А вот и проблема.Так как IEEE 754 double s имеет 53-битные мантиссы;первый бит равен 1 для нормальных чисел;остальные 52 бита делятся поровну на 4, реализация, следующая за этой сноской (Glibc на моей машине, кажется, равен одному), всегда выведет любое конечное ненулевое число с плавающей запятой, так что оно начинается с0x1.!

Попробуйте, например, эту минимальную программу:

#include <stdio.h>

int main(void) {
    for (double i = 1; i < 1024 * 1024; i *= 2) {
        printf("%a %a %a\n", 1.0 * i, 0.7 * i, 0.67 * i);
    }
}

Вывод которой на моем компьютере равен

0x1p+0 0x1.6666666666666p-1 0x1.570a3d70a3d71p-1
0x1p+1 0x1.6666666666666p+0 0x1.570a3d70a3d71p+0
0x1p+2 0x1.6666666666666p+1 0x1.570a3d70a3d71p+1
0x1p+3 0x1.6666666666666p+2 0x1.570a3d70a3d71p+2
0x1p+4 0x1.6666666666666p+3 0x1.570a3d70a3d71p+3
0x1p+5 0x1.6666666666666p+4 0x1.570a3d70a3d71p+4
0x1p+6 0x1.6666666666666p+5 0x1.570a3d70a3d71p+5
0x1p+7 0x1.6666666666666p+6 0x1.570a3d70a3d71p+6
0x1p+8 0x1.6666666666666p+7 0x1.570a3d70a3d71p+7
0x1p+9 0x1.6666666666666p+8 0x1.570a3d70a3d71p+8
0x1p+10 0x1.6666666666666p+9 0x1.570a3d70a3d71p+9
0x1p+11 0x1.6666666666666p+10 0x1.570a3d70a3d71p+10
0x1p+12 0x1.6666666666666p+11 0x1.570a3d70a3d71p+11
0x1p+13 0x1.6666666666666p+12 0x1.570a3d70a3d71p+12
0x1p+14 0x1.6666666666666p+13 0x1.570a3d70a3d71p+13
0x1p+15 0x1.6666666666666p+14 0x1.570a3d70a3d71p+14
0x1p+16 0x1.6666666666666p+15 0x1.570a3d70a3d71p+15
0x1p+17 0x1.6666666666666p+16 0x1.570a3d70a3d71p+16
0x1p+18 0x1.6666666666666p+17 0x1.570a3d70a3d71p+17
0x1p+19 0x1.6666666666666p+18 0x1.570a3d70a3d71p+18

Этот вывод эффективен - для каждого нормального кода, который должен вывести код, нужно всего 0x1., за которым следуют все фактические кусочки мантиссы, преобразованные в шестнадцатеричный код, завершающие полосу 0 символов и добавляющие p+, за которыми следуетэкспонента.


Для длинных парных чисел формат x86 имеет 64 бит мантиссы.Поскольку 64 бита делятся на полубайты, разумная реализация будет иметь полный полубайт, предшествующий . для нормальных чисел, со значениями, варьирующимися от 0x8 до 0xF (первый бит всегда равен 1), и до точки следует до 15 кусков.

Попробуйте выполнить реализацию с

#include <stdio.h>
int main(void) {
    for (long double i = 1; i < 32; i ++) {
        printf("%La\n", i);
    }
}

, чтобы проверить, соответствует ли это ожидание ...


Между положительными нормальными числами и нулем могут быть субнормальные числа - мой Glibc представляет эти двойные значения с 0x0., за которым следуют фактические кусочки мантиссы с удаленными конечными нулями и фиксированным показателем -1022 -Опять же, представление это то, что проще всего реализовать и быстрее всего вычислить.

0 голосов
/ 04 июня 2018

Но оба имеют одинаковое значение 10364. Десятичное число.

Действительно.

Что происходит?Почему изменилось выходное значение?

Почему не должно меняться?Представление double в памяти не несет никакой информации о форматировании.И, как вы сами заметили, выходные данные представляют собой то же число, что и входные данные, поэтому значение не изменилось .Он просто представлен по-разному.

Примерно аналогичное поведение может произойти и с десятичными числами, используя директивы %e.

Как получить исходное число в качестве выходного?

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

В комментариях, которые вы добавляете,

Но что такое стандартное представление?

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

0 голосов
/ 03 июня 2018

Это шестнадцатеричный формат с плавающей точкой.Цифры (и точка) после 0x и до p представляют собой шестнадцатеричное число.Эта часть называется значимостью.Цифры после p представляют собой десятичную цифру, обозначающую степень 2, на которую умножается значимое.

В 0xa.1fp10 значение значится a.1f.Это представляет число 10 • 16 0 + 1 • 16 -1 + 15 • 16 -2 , что равно 10 + 31/256 или 2591 /256.

Тогда p10 говорит, что нужно умножить это на 2 1024 , так что результат будет 2591/256 • 1024 = 10,364.

Результатом будет только число,0xa.1fp10, 10364 и 0x1.43ep13 - это три разные цифры, которые представляют одно и то же число.Когда вы сохраняете это значение в float или double, объект содержит только число.Там нет записи его оригинального формата.Когда вы печатаете его с помощью %a, реализация выбирает начальную цифру 1 .Поскольку нет записи оригинальной цифры, нет способа заставить printf создать оригинальную строку, если у вас нет отдельной записи этой информации и вы пишете свое собственное программное обеспечение для печати цифры.

Форматы с плавающей точкой часто используют двоичную базу, и трудно написать хорошее программное обеспечение, которое правильно преобразует десятичную научную запись в двоичную с плавающей точкой.(Это решаемая проблема с опубликованными статьями, но хорошее программное обеспечение не всегда использовалось.) Использование шестнадцатеричного формата вместо десятичного позволяет легко точно указать значение, которое автор хочет получить в числе с плавающей запятой, и компилятору легкоинтерпретировать это.Шестнадцатеричный формат предназначен для этой цели: легкость и точность чтения и записи чисел с плавающей запятой.Он не предназначен для облегчения эстетических проблем, таких как воспроизведение определенного масштабирования или нормализации.

Сноска

1 Когда используется %a, стандарт C оставляет его без вниманияв реализации, чтобы выбрать используемое масштабирование, за исключением того, что перед символом десятичной запятой стоит ровно одна цифра, оно ненулевое, если число находится в нормальном диапазоне формата с плавающей запятой, и количество цифр после запятойравна точности.

...