Подтвердить что ты не робот

Как% конверсии работает в инструкции printf?

Может быть, это не относится к SO, но я не знаю, где еще.

Мне нужно переопределить printf(3) с помощью C без использования какой-либо функции, которая будет делать преобразование для меня, я почти готов, но я застрял на %a, я действительно не понимаю, что такое например, здесь:

printf("%a\n", 3.0); //#=> 0x1.8p+1
printf("%a\n", 3.1); //#=> 0x1.8cccccccccccdp+1
printf("%a\n", 3.2); //#=> 0x1.999999999999ap+1
printf("%a\n", 3.3); //#=> 0x1.a666666666666p+1
printf("%a\n", 3.4); //#=> 0x1.b333333333333p+1
printf("%a\n", 3.5); //#=> 0x1.cp+1
printf("%a\n", 3.6); //#=> 0x1.ccccccccccccdp+1

Конечно, я читаю человека, который говорит:

Двойной аргумент округляется и преобразуется в шестнадцатеричную нотацию в стиле [-] 0xh.hhhp [+ -] d, где число цифр после символ шестнадцатеричной точки равен спецификации точности.

Но это действительно не помогает, я не понимаю процесс, который преобразует 3.2 в 1.999999999999ap+1

Мне не нужен какой-либо код, но действительно более объяснение.

PS: Если это не место для этого вопроса, можете ли вы указать мне нужное место?

EDIT: пока @juhist ответ работает для чисел >= 1.0, он не объясняет, как получить результат для чисел между 0.0 et 1.0:

printf("%a\n", 0.01); //#=> 0x1.47ae147ae147bp-7
printf("%a\n", 0.1);  //#=> 0x1.999999999999ap-4
printf("%a\n", 0.2);  //#=> 0x1.999999999999ap-3
printf("%a\n", 0.3);  //#=> 0x1.3333333333333p-2
printf("%a\n", 0.4);  //#=> 0x1.999999999999ap-2
printf("%a\n", 0.5);  //#=> 0x1p-1
printf("%a\n", 0.6);  //#=> 0x1.3333333333333p-1

Также мне бы очень хотелось получить точность этого The "a" near the end occurs due to limited floating point calculation precision относительно преобразования: printf("%a\n", 3.2); //#=> 0x1.999999999999ap+1

EDIT2: Теперь последняя загадка объясняет, почему в этом случае:

printf("%a\n", 0.1); //#=> 0x1.999999999999ap-4

Последний 9 становится и a, и в этом случае:

printf("%a\n", 0.3); //#=> 0x1.3333333333333p-2

В последнем 3 остается a 3?

4b9b3361

Ответ 1

Сначала вам нужно знать, что означает представление 0xh.hhhh p±d,? Поясним это, взяв пример шестнадцатеричной константы 0x1.99999ap+1.
Цифра 1 до десятичной точки - это шестнадцатеричная цифра, а число шестнадцатеричных цифр после него (99999a) равна точности. 0x - это шестнадцатеричный интродуктор, а p - поле экспоненты. Показатель - это десятичное число, указывающее мощность 2, по которой значительная часть умножается.

Итак, когда 0x1.99999ap+1 будет умножено на 2 1 тогда он будет преобразован в 3.2 в десятичном формате. Напомним, что 1.55e+1 преобразован в 15.500000 в десятичном формате. Подобное происходит здесь.

Теперь вам нужно знать математику за преобразованием 0x1.99999ap+1 в 3.2. Это будет действовать следующим образом.

1 * 16 0 + 9 * 16 -1 + 9 * 16 -2 + 9 * 16 -3 + 9 * 16 -4 + 9 * 16 -5 + 10 * 16 -1

Который (в десятичной форме) равен

 1.60000002384185791015625  

Вам нужна только до 1 точности. Итак, возьмите 1.6 и умножьте его на 2 1. Что даст 3.2.

Чтобы перейти к обратному процессу выше, вам нужно найти мощность 2, по которой число с плавающей запятой будет разделено, чтобы получить цифру 1 до десятичной точки. После этого используйте последовательные умножения для изменения дробной части на шестнадцатеричную фракцию. Выполните следующие действия:

  • 3.2/2 1= 1.6
  • Возьмите неотъемлемую часть от 1.6, чтобы получить шестнадцатеричную цифру 1 до десятичной точки.
  • Умножьте .6 на 16. Полученная интегральная часть станет цифрой в шестнадцатеричной фракции. Повторите этот шаг с полученной дробной частью до нужной точности (по умолчанию - 6).
    •   
    • .6 * 16 = 9.6 --- > Интегральная часть = 9 Дробная часть =.6  
    • .6 * 16 = 9.6 --- > Интегральная часть = 9 Дробная часть =.6  
    • .6 * 16 = 9.6 --- > Интегральная часть = 9 Дробная часть =.6  
    • .6 * 16 = 9.6 --- > Интегральная часть = 9 Дробная часть =.6  
    • .6 * 16 = 9.6 --- > Интегральная часть = 9 Дробная часть =.6  
    • .6 * 16 = 9.6 --- > Интегральная часть = 9 Дробная часть =.6  
      

Итак, шестнадцатеричная дробь станет .999999 Теперь комбинируем шестнадцатеричный указатель 0x, шестнадцатеричную цифру перед десятичной точкой и шестнадцатеричную дробь, полученную вместе с полем экспоненты, чтобы получить результат.

3.2 10= 0x1.999999p + 1 16

Аналогичным образом вы можете получить шестнадцатеричное число с плавающей запятой для чисел меньше 1.0., например 0.01. В этом случае для получения шестнадцатеричной цифры 1 до десятичной точки вам нужно разделить ее на число, которое имеет значение 2. Так как 128 (2 5) после умножения на 0.01 даст число, интегральная часть которого становится 1, 128*.01 = 1.28. Это означает, что вам нужно умножить 0.01 на 1/2 -5 или вы можете сказать, что вам нужно разделить его на 2 -5 чтобы получить 1.28. Теперь примените описанные выше шаги 2 и 3.

Ответ 2

Это простой вопрос для ответа.

Очевидное объяснение состоит в том, что (1 + 9,0/16 + 9,0/(16 * 16) + 9,0/(16 * 16 * 16) + 9,0/(16 * 16 * 16 * 16) +...) * 2 = 3,2

Вы можете легко проверить это, взяв пять первых терминов и напишите это интерпретатору Python: (1 + 9,0/16 + 9,0/(16 * 16) + 9,0/(16 * 16 * 16) + 9,0/(16 * 16 * 16 * 16)) * 2

Ответ: 3.199981689453125.

Почему 2 в конце? Конечно, поскольку (1 < 1) = 2, а число после "р" равно +1. Если бы вы имели бы +2, то вместо этого вы использовали бы (1 < 2) = 4 в качестве множителя.

EDIT: Хорошо, поэтому необходим противоположный алгоритм.

Сначала найдите величину сдвига x, для которой (3.2/(2 ^ x)) находится между 1 и 2. В этот случай, 3.2/(2 ^ 1) = 3.2/2 = 1.6.

Затем выведите "1." и вычесть 1 из результата, давая 0,6

Затем умножьте результат на 16 и возьмите целочисленную часть. Продукт 9.6. Выход "9".

Затем вычтите результат из результата. 9,6 - 9 = 0,6.

Повторите: умножьте результат на 16 и возьмите целочисленную часть. Продукт 9.6. Выход "9".

Снова вычитаем результат из результата. 9,6 - 9 = 0,6

Повторите эти процессы. Ad infinitum, если вы хотите полного расширения, но в Практика, вы остановите итерацию где-нибудь. В конце вы выводите "p" и "+1", потому что мощность была +1. "A" вблизи конца происходит из-за ограниченной точности вычисления с плавающей запятой.

Другое редактирование: чтобы в полной мере понять, какие последствия имеет ограниченная точность, вы можете прочитать "Что каждый компьютерный ученый должен знать о арифметике с плавающей точкой", очень известный документ, который имеет онлайн-версию, например. там: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Пример алгоритма для числа 0,1: находим, что 0,1/(2 ^ (- 4)) = 0,1 * 2 ^ 4 = 0,1 * 16 = 1,6, которое находится между 1 и 2, поэтому мы выбираем мощность - 4. Итак, мы выводим "1." а затем вычислить (1.6-1) = 0.6 и умножить на 16: 16 * 0.6 = 9.6. Итак, мы выводим "9", вычитаем 9,6 - 9 = 0,6 и снова умножим его на 16, давая еще один "9". И так далее. Итак, ответ "1.9999999.... p-4" дает неограниченную точность с плавающей запятой. Но из-за ограниченной точности, похоже, последняя буква "а". Таким образом, алгоритм также работает для отрицательных мощностей, но тогда вы должны заметить, что деление на отрицательную мощность такое же, как умножение на положительную мощность. Таким образом, вам нужно также учитывать отрицательные числа при выборе мощности, которая дает значение от 1 до 2.