Длинный двойной в C

Я прочитал книгу C Primer Plus и добрался до этого примера

#include <stdio.h>
int main(void)
{
    float aboat = 32000.0;
    double abet = 2.14e9;
    long double dip = 5.32e-5;

    printf("%f can be written %e\n", aboat, aboat);
    printf("%f can be written %e\n", abet, abet);
    printf("%f can be written %e\n", dip, dip);

    return 0;
}

После того, как я запустил это на своем macbook, я был в шоке от результата:

32000.000000 can be written 3.200000e+04
2140000000.000000 can be written 2.140000e+09
2140000000.000000 can be written 2.140000e+09

Итак, я осмотрелся и обнаружил, что правильный формат для отображения long double — использовать %Lf. Однако я до сих пор не могу понять, почему я получил двойное значение abet вместо того, что получил при запуске на Cygwin, Ubuntu и < em>iDeneb, что примерно

-1950228512509697486020297654959439872418023994430148306244153100897726713609
013030397828640261329800797420159101801613476402327600937901161313172717568.0
00000 can be written 2.725000e+02

Любые идеи?


person reubensammut    schedule 22.11.2009    source источник
comment
Вы уверены, что у вас был именно этот код? Я только что запустил его на своем MacBook — MacOS X 10.5.8 (GCC 4.0.1) и получил ваш результат Cygwin.   -  person Jonathan Leffler    schedule 22.11.2009
comment
Да я только что перепроверил. Кстати, я использую MacOS X 10.6.2 (GCC 4.2.1)   -  person reubensammut    schedule 22.11.2009
comment
На самом деле я получаю ожидаемый бессмысленный ответ на Mac. Загадочный. Запуск otool -L в моем исполняемом файле сообщает мне, что он работает с /usr/lib/libSystem.B.dylib версии 111.1.4; это библиотека, которая предоставляет printf. Если у вас такая же версия этой библиотеки, вы должны получить тот же ответ.   -  person Jason Orendorff    schedule 22.11.2009
comment
Джейсон Орендоррф предлагает 64-битный ABI; скомпилировав код с помощью gcc -m64, я могу воспроизвести результат. Моя немедленная реакция: «ошибка в комбинации компилятор/библиотека», но это, вероятно, неправильно. Он интерпретирует строку правильного формата - я преобразовал текст в верхний регистр, и ошибочный («дублированный») ответ появился в верхнем регистре. Вероятно, это связано с соглашениями о вызовах.   -  person Jonathan Leffler    schedule 22.11.2009
comment
Вывод gcc -S имеет только 10 общих строк между 32-битным и 64-битным ассемблером. Вы получите разные результаты, если сначала напечатаете длинное двойное число. По сути, задавая 'printf()' неправильный спецификатор формата для аргументов (или неправильные аргументы для спецификатора формата), вы вызываете "неопределенное" поведение; то, что вы видите, является результатом этого.   -  person Jonathan Leffler    schedule 22.11.2009


Ответы (5)


Попробуйте взглянуть на соглашение о вызовах varargs в OSX, это может объяснить это.

Я предполагаю, что компилятор передает первый параметр long double в стеке (или в регистре FPU) и первый параметр double в регистрах ЦП (или в стеке). Так или иначе, они проходят в разных местах. Таким образом, когда выполняется третий вызов, значение из второго вызова все еще лежит без дела (и вызываемый объект подбирает его). Но это всего лишь предположение.

person Steve Jessop    schedule 22.11.2009

Функция printf() C Standard Library является примером вариационная функция, которая может принимать разное количество аргументов. В соответствии с тем, как это реализовано в языке C, вызываемая функция должна знать, какой тип аргументов и в каком порядке был передан, чтобы она могла правильно их интерпретировать. Вот почему вы передаете строку формата, чтобы printf() мог правильно понять данные, которые он должен напечатать.

Если функция с переменным числом аргументов неправильно интерпретирует переданные ей аргументы, стандарт C указывает, что поведение не определено, то есть может произойти что угодно (пункт 4.8.1.2 стандарта C89). В вашем случае, когда вы передаете несоответствующие форматы и значения printf(), это то, что происходит. Однако, если у вас есть достойный компилятор и ваши уровни предупреждений оказались чем-то разумным, вы должны быть предупреждены об этом во время компиляции. Например, на Cygwin я получаю:

$ make go
cc -g -W -Wall -Wwrite-strings -ansi -pedantic    go.c   -o go
go.c: In function `main':
go.c:10: warning: double format, long double arg (arg 2)
go.c:10: warning: double format, long double arg (arg 3)
go.c:10: warning: double format, long double arg (arg 2)
go.c:10: warning: double format, long double arg (arg 3)
$ 

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

person Tim    schedule 22.11.2009
comment
Также обратите внимание, что в функциях с переменным числом аргументов float параметры всегда повышаются и передаются как double. Интегральные параметры char и short повышаются до int (или unsigned int). - person tomlogic; 07.06.2010
comment
@tomlogic: у K&R была веская причина для приведения всех значений с плавающей запятой к одному типу в функциях с переменным числом аргументов; ANSI C должен был последовать этому примеру, позволив прототипам назначать тип с плавающей запятой, в который будут преобразованы все значения с плавающей запятой. Многие компиляторы для машин, которые выполняли вычисления с использованием 80-битных вычислений с плавающей запятой, работали над отсутствием такой функции, делая long double 64-битные и не предоставляя 80-битный тип, что приводило к ухудшению математики с плавающей запятой, которая продолжается до сих пор. день. - person supercat; 05.06.2015

Используйте спецификатор как %LF для long double вместо %lf или %f. %LF всегда имеет другое значение, чем %lf.

#include <stdio.h>
int main(void)
{
    float aboat = 32000.0;
    double abet = 2.14e9;
    long double  dip = 5.32e-5L;

    printf("%f can be written %e\n", aboat, aboat);
    printf("%f can be written %e\n", abet, abet);
    printf("%LF can be written %LE\n", dip, dip);

    return 0;
}

Вывод:

Вывод; стенограмма ниже

32000.000000 can be written 3.200000e+04
2140000000.000000 can be written 2.140000e+09
0.000053 can be written 5.320000E-05
person Dhrumil Shah    schedule 11.03.2015
comment
Это правильно, но f vs F влияет только на регистр. Важная разница между %lf и %LF заключается в том, что между l (ell) и L. L указывает long double, l (ell) указывает long integer. l (ell) в %lf игнорируется. F и f идентичны, за исключением того, что F печатает INF и NAN в верхнем регистре, а f печатает inf и nan в нижнем регистре. - person Charles Nicholson; 06.07.2019

Возможно, 64-битный ABI отличается таким образом, что printf ищет аргументы %f в совершенно другом месте, чем аргументы %LF.

Попробуйте посмотреть выходные данные сборки (gcc -S), чтобы убедиться, что это правда.

person Jason Orendorff    schedule 22.11.2009
comment
Я посмотрю на сборку и посмотрю, так ли это, хотя это может занять некоторое время, потому что я не очень уверен в сборке, особенно с x86-64. - person reubensammut; 22.11.2009

Я читаю C Primer Plus, как и вы, я заметил то же самое. Посмотрите, как я изменил спецификаторы формата для третьего оператора printf.

#include <stdio.h>
#include <inttypes.h>

int main(void){

    float aboat = 320000.0;
    double abet = 2.214e9;
    long double dip = 5.32e-5;

    printf("%f can be written %e\n", aboat, aboat);
    printf("%f can be written %e\n", abet, abet);
    printf("%Lf can be written %Le\n", dip, dip);

    return 0;
}

Результаты после изменения спецификаторов формата

320000.000000 can be written 3.200000e+05
2214000000.000000 can be written 2.214000e+09
0.000053 can be written 5.320000e-05
person RPitre    schedule 06.06.2010