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

Какие переменные следует использовать при выполнении математических операций в C/С++?

Например, когда я разделяю два int и хочу, чтобы возвращался float, я суеверно писал что-то вроде этого:

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

Если я не бросаю a и b в float, он будет целочисленным делением и возвратит int.

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

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

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

4b9b3361

Ответ 1

Вопрос 1: Float division

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

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

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

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

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

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

Переполнение

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

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

std::numeric_limits<int>::max() / -1  // No Overflow
std::numeric_limits<int>::min() / -1  // Will Overflow

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

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

#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;
}

Ответ 5

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

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

Ответ 6

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

Итак, все:

float c = (float) a/b;

float c = a/(float) b;

float c = (float) a/(float) b;

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

Ответ 7

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

int a;
int b;
float z;

z = a*1.0*b;

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

Ответ 8

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

Ответ 9

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

например. этот литой float- > int очевиден.

int a = float(foo()) * float(c);