Какие переменные я должен типизировать при выполнении математических операций в C / C ++? - PullRequest
18 голосов
/ 29 октября 2008

Например, когда я делю два целых числа и хочу вернуть число с плавающей точкой, я суеверно пишу что-то вроде этого:

int a = 2, b = 3;
float c = (float)a / (float)b;

Если я не разыграю a и b для чисел с плавающей запятой, он выполнит целочисленное деление и вернет int.

Точно так же, если я хочу умножить 8-разрядное число со знаком на 8-разрядное число без знака, я приведу их к 16-разрядным числам со знаком перед умножением, опасаясь переполнения:

u8 a = 255;
s8 b = -127;
s16 = (s16)a * (s16)b;

Как именно компилятор ведет себя в этих ситуациях, когда он вообще не приводится или когда приводится только одна из переменных? Мне действительно нужно явно привести все переменные, или только одну слева или одну справа?

Ответы [ 9 ]

26 голосов
/ 29 октября 2008

Вопрос 1: деление поплавка

int a = 2, b = 3;
float c = static_cast<float>(a) / b;  // need to convert 1 operand to a float

Вопрос 2: Как работает компилятор

Пять правил для запоминания:

  • Арифметические операции всегда выполняются над значениями одного типа.
  • Тип результата совпадает с операндами (после повышения)
  • Арифметические операции наименьшего типа выполняются над int.
  • ANSCI C (и, следовательно, C ++) использует целочисленное продвижение с сохранением значения.
  • Каждая операция выполняется изолированно .

Правила ANSI C следующие:
Большинство из этих правил также применимы к C ++, хотя не все типы официально поддерживаются (пока).

  • Если один из операндов имеет значение long double , то другой преобразуется в long double .
  • Если один из операндов является double , другой преобразуется в double .
  • Если один из операндов является с плавающей точкой , другой преобразуется в с плавающей точкой .
  • Если один из операндов имеет длинную без знака , то другой преобразуется в длинную без знака .
  • Если один из операндов имеет длину long , другой преобразуется в long long .
  • Если один из операндов имеет длинную без знака , то другой преобразуется в длинную без знака .
  • Если один из операндов имеет длину , другой преобразуется в long .
  • Если один из операндов является без знака int , то другой преобразуется в без знака int .
  • В противном случае оба операнда преобразуются в int .

Перелив

Переполнение - это всегда проблема. Заметка. Тип результата совпадает с операндами ввода, поэтому все операции могут быть переполнены, так что да, вам нужно об этом беспокоиться (хотя язык не предоставляет какого-либо явного способа уловить это.

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

std::numeric_limits<int>::max() / -1  // No Overflow
std::numeric_limits<int>::min() / -1  // Will Overflow
13 голосов
/ 29 октября 2008

В общем, если операнды бывают разных типов, компилятор переведет все в самый большой или самый точный тип:

If one number is...   And the other is...    The compiler will promote to...
-------------------   -------------------    -------------------------------
char                  int                    int
signed                unsigned               unsigned
char or int           float                  float
float                 double                 double

Примеры:

char       + int             ==> int
signed int + unsigned char   ==> unsigned int
float      + int             ==> float

Остерегайтесь, однако, что повышение по службе происходит только так, как требуется для каждого промежуточного расчета, поэтому:

4.0 + 5/3  =  4.0 + 1 = 5.0

Это связано с тем, что сначала выполняется целочисленное деление, а затем добавляется результат с плавающей запятой.

5 голосов
/ 29 октября 2008

Вы можете просто разыграть одного из них. Неважно, какой именно.

Всякий раз, когда типы не совпадают, «меньший» тип автоматически переводится в «больший» тип с плавающей точкой «больше», чем целочисленные типы.

2 голосов
/ 29 октября 2008

Деление целых чисел: разыграть любой из операндов, не нужно разыгрывать оба. Если оба операнда являются целыми числами, операция деления является целочисленным делением, в противном случае это деление с плавающей запятой.

Что касается вопроса о переполнении, нет необходимости явно приводить, поскольку компилятор делает это за вас неявно:

#include <iostream>
#include <limits>

using namespace std;
int main()
{
    signed int a = numeric_limits<signed int>::max();
    unsigned int b = a + 1; // implicit cast, no overflow here
    cout << a << ' ' <<  b << endl;
    return 0;
}
1 голос
/ 29 октября 2008

Работая над системами, критически важными для безопасности, я склонен быть параноиком и всегда использую оба фактора: float (a) / float (b) - на тот случай, если какой-нибудь тонкий хит-парад планирует укусить меня позже. Неважно, насколько хорош компилятор, как бы хорошо он ни описывался, детали в официальных спецификациях языка. Паранойя: лучший друг программиста!

1 голос
/ 29 октября 2008

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

int a;
int b;
float z;

z = a*1.0*b;

Конечно, это не универсально, хорошо только для этого случая.

1 голос
/ 29 октября 2008

Я думаю, что пока вы приводите только одну из двух переменных, компилятор будет работать правильно (по крайней мере, на тех компиляторах, которые я знаю).

Итак, всего:

float c = (плавать) a / b;

float c = a / (float) b;

float c = (плавать) a / (плавать) b;

будет иметь тот же результат.

1 голос
/ 29 октября 2008

В случае деления с плавающей запятой, если одна переменная имеет тип данных с плавающей запятой (с плавающей запятой или двойной), то другая переменная должна быть расширена до типа с плавающей запятой, и деление с плавающей запятой должно произойти; так что нет необходимости разыгрывать оба на поплавок.

Сказав это, я всегда бросаю оба на поплавок.

0 голосов
/ 29 октября 2008

Вам нужно разыграть одну или две стороны? Ответ не продиктован компилятором. Он должен знать точные, предварительные правила. Вместо этого ответ должен быть продиктован человеком, который будет читать код позже. Только по этой причине приведите обе стороны к одному типу. Неявное усечение может быть достаточно видимым, поэтому приведение может быть избыточным.

например. этот бросок float-> int очевиден.

int a = float(foo()) * float(c); 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...