long long против int умножения - PullRequest
       9

long long против int умножения

7 голосов
/ 16 августа 2010

С учетом следующего фрагмента:

#include <stdio.h>

typedef signed long long int64;
typedef signed int int32;
typedef signed char int8;

int main()
{
    printf("%i\n", sizeof(int8));
    printf("%i\n", sizeof(int32));
    printf("%i\n", sizeof(int64));

    int8 a = 100;
    int8 b = 100;
    int32 c = a * b;
    printf("%i\n", c);

    int32 d = 1000000000;
    int32 e = 1000000000;
    int64 f = d * e;
    printf("%I64d\n", f);
}

Выход с MinGW GCC 3.4.5 равен (-O0):

1
4
8
10000
-1486618624

Первое умножение преобразуется внутри int32 (согласно выводу ассемблера).Второе умножение не приведено.Я не уверен, отличаются ли результаты, потому что программа работала на IA32, или потому что она определена где-то в стандарте C.Тем не менее меня интересует, определено ли это точное поведение где-либо (ISO / IEC 9899?), Потому что мне нравится лучше понимать, почему и когда я должен выполнять приведение вручную (у меня проблемы с переносом программы из другой архитектуры).

Ответы [ 4 ]

7 голосов
/ 16 августа 2010

Стандарт C99 определяет, что бинарные операторы, такие как *, не работают с целочисленными типами, меньшими int.Выражения этих типов повышаются до int до применения оператора.См. Пункт 6.3.1.4 и многочисленные вхождения слов «целочисленное продвижение».Но это несколько ортогонально инструкциям сборки, сгенерированным компилятором, которые работают на int s, потому что это быстрее, даже когда компилятору будет позволено вычислить более короткий результат (потому что результат сразу сохраняется в l-значениинапример, короткий тип).

Что касается int64 f = d * e;, где d и e имеют тип int, умножение выполняется как int в соответствии с теми же правилами продвижения.Технически это переполнение неопределенное поведение , вы получаете здесь результат с двумя дополнениями, но вы можете получить что-нибудь в соответствии со стандартом.

Примечание: правила продвижения различают подписанный и неподписанный типыпри продвижении.Правило состоит в том, чтобы выдавать меньшие типы на int, если int не может представлять все значения типа, в этом случае используется unsigned int.

5 голосов
/ 16 августа 2010

Проблема в том, что умножение - это int32 * int32, которое делается как int32, а результат затем присваивается int64. Вы получите почти такой же эффект с double d = 3 / 2;, который разделит 3 на 2 с помощью целочисленного деления, и присваивает 1,0 для d.

Вы должны обращать внимание на тип выражения или подвыражения всякий раз, когда это может иметь значение. Для этого необходимо убедиться, что соответствующая операция рассчитана как соответствующий тип, например, приведение одного из мультипликаторов к int64 или (в моем примере) 3.0 / 2 или (float)3 / 2.

3 голосов
/ 16 августа 2010

Читайте K & R (оригинал).Все целочисленные операции выполняются с натуральным целочисленным типом, если только он не включает переменные, которые (или преобразуются) в нечто большее.Операции над символом приводятся к 32 битам, потому что это естественный размер целого числа в этой архитектуре.Умножение двух 32-разрядных целых чисел выполняется в 32-разрядных, потому что ничто не приводит его к чему-либо большему (пока вы не назначите его для 64-разрядной переменной, но это слишком поздно).Если вы хотите, чтобы операция выполнялась в 64 битах, приведите один или оба целых числа к 64 битам.

int64 f = (int64)d * e;
2 голосов
/ 16 августа 2010

a * b вычисляется как целое число, а затем приводится к типу принимающей переменной (который просто оказывается int)

d * e вычисляется как целое число, а затем приводится к получающей переменнойtype (который просто имеет значение int64)

Если бы любая из переменных типа была больше, чем int (или была плавающей точкой), чем этот тип, был бы использован.Но так как все типы, использованные в умножениях, были int или меньше, были использованы целые числа.

...