Какого типа `1.`?

352
26 января 2017, 03:01

В такой программе на Си

double x = 1;
x %= 1.;

получаю сообщение об ошибке http://ideone.com/dDurEH

invalid operands to binary % (have 'double' and 'long double')

из которого следует, что выражение 1. имеет тип long double.

Тот же код, но на плюсах показывает просто double http://ideone.com/nd49YK:

invalid operands of types 'double' and 'double' to binary 'operator%'

Да и такая проверка на плюсах тоже за double http://ideone.com/HCw0GG:

auto x = 1.;
cout 
<< sizeof (double) << ' '      // 8
<< sizeof (long double) << ' ' // 12
<< sizeof x << ' '             // 8
<< sizeof 1. << endl;          // 8

Получается, С++ выводит тип double, а не long double.

Итак, это одни из различий Си и Си++ или же косяк компилятора?
Какой правильный тип у константы 1. и зависит ли он от языка?

Answer 1

Константа вида 1. имеет тип double как в C++, так и в C.

  • C

    6.4.4.2/4: An unsuffixed floating constant has type double. If suffixed by the letter for F, it has type float. If suffixed by the letter l or L, it has type long double.

  • C++

    2.14.4/1: The type of a floating literal is double unless explicitly specified by a suffix.

Компилятор, используемый на ideone.com имеет версию 5.1.1 20150711. Так что может иметь смысл погонять её локально, если у кого есть возможность. Похожий компилятор на goodbolt (5.1) выдает ошибку с правильными типами:

error: invalid operands of types 'double' and 'double' to binary 'operator%'
error: in evaluation of 'operator%=(double, double)' 

Возможная причина кроется в значении FLT_EVAL_METHOD, которое задает точность вычислений для внутренних операций. Для ideone это значение равно 2, т.к. используется 32-битный компилятор:

For gcc, FLT_EVAL_METHOD == 2 is the default on 32 bit x86

Т.е. все вычисления с плавающей точкой опираются на тип long double.

Хотя, по правде говоря, это и не должно влиять на тип вещественной константы.

Как оказалось, изменить значение FLT_EVAL_METHOD с 0 на 2 на 64 bit компиляторе g++ можно путём добавления ключа -mfpmath=387:

#include <stdio.h>
#include <float.h>
int main()
{
    // 1 % 1.;
    printf( "%d\n", FLT_EVAL_METHOD);
}

Результат:

2

В этом случае сообщение об ошибке для второго аргумента также выводит тип long double как и исходное на ideone (для 32 bit):

invalid operands to binary % (have 'int' and 'long double')

Для clang нужен другой ключ: -mno-sse. При этом диагностическое сообщение уже для обоих типов выводит double:

invalid operands to binary expression ('double' and 'double')

При этом первый, как мне кажется, должен всё же был остаться int.

Подводя итог, можно сказать, что оба компилятора в диагностических сообщениях имеют проблемы с выводом исходного типа операндов.

Answer 2

Константа с плавающей запятой 1. имеет тип double.

Подобные сообщения об ошибках, как данное

invalid operands to binary % (have 'double' and 'long double'

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

Из стандарта C (5.2.4.2.2 Characteristics of floating types <float.h>)

9 Except for assignment and cast (which remove all extra range and precision), the values yielded by operators with floating operands and values subject to the usual arithmetic conversions and of floating constants are evaluated to a format whose range and precision may be greater than required by the type. The use of evaluation formats is characterized by the implementation-defined value of FLT_EVAL_METHOD:24)

-1 indeterminable; 0 evaluate all operations and constants just to the range and precision of the type;

1 evaluate operations and constants of type float and double to the range and precision of the double type, evaluate long double operations and constants to the range and precision of the long double type;

2 evaluate all operations and constants to the range and precision of the long double type.

All other negative values for FLT_EVAL_METHOD characterize implementation-defined behavior.

Если включить заголовок , где имя FLT_EVAL_METHOD определено, то можно получить следующий результат

#include <stdio.h>
#include <float.h>
int main(void) 
{
    printf( "%d\n",  FLT_EVAL_METHOD );
    return 0;
}

Вывод на консоль:

2

Как следует из вышеприведенной цитаты из стандарта C это означает, что константы будут преобразованы к типу long double.

Если заголовочный файл <float.h> не включен, тем не менее, видимо, компилятор использует данную опцию по умолчанию для вычисления выражений с плавающей запятой.

Answer 3

Решил проверить через sizeof http://ideone.com/r54ZSs:

#include <stdio.h>
int main()
{
    printf("%d %d %d", sizeof (double), sizeof (long double), sizeof 1.);
    return 0;
}

Получил 8 12 8. Т. е. это обычный double.

Судя по всему, в анализ ошибки закралась какая-то ошибка, потому что только там он получает другой тип http://ideone.com/45lztk:

int main()
{
    int x = 1;
    x %= 1.;         // error: invalid operands to binary % (have 'int' and 'long double')
    x %= (double)1.; // error: invalid operands to binary % (have 'int' and 'double')
    return 0;
}
READ ALSO
Как хранятся в памяти литералы?

Как хранятся в памяти литералы?

Как хранятся в памяти литералы?

311
Как построить native android программу через cmd в Windows? [требует правки]

Как построить native android программу через cmd в Windows? [требует правки]

Какиеexe нужно использовать? Из папки x86_64 или arm-linux-androideabi или других? и т

280
Переопределение макроса

Переопределение макроса

Подскажите, можно ли определить макрос так, чтобы в зависимости от того, использовался он, или нет, он принимал разные значения ?

312