При использовании математических операторов в программировании на языке C очень важно использовать приведения или правильно определять размер переменной. Мне нужна помощь в этом.
#include <stdio.h>
#include <stdint.h>
int main(void)
{
uint32_t a;
uint8_t b;
uint8_t d;
uint64_t c;
float cd;
a = 4294967295;
b = 2;
d = 2;
c = a * b * d;
cd = c;
printf("%f\n", cd);
return 0;
}
Переменная результата достаточно велика, чтобы хранить 2 * 2 * uint32_max
. Однако я заметил, что переменная b
или d
должна иметь ширину 64 бита (или использовать приведение), чтобы получить правильный результат. На этот раз я подумал, что математические операции происходят в переменной результата, но похоже, что это не так. Может ли кто-нибудь объяснить мне, какую переменную нужно расширить (b или d) и какова теоретическая основа этого?
Как обстоят дела в дивизии? Должен ли я подумать, хочу ли я разделить 32-битное число на 8-битное. будет ли результат в этом случае только 8-битным? Есть ли какое-либо правило о типе знаменателя?
Никакая переменная не нуждается в расширении. Вы также можете ввести c = (uint64_t)a * b * c;
.
@ user1810087 Это не «расширение» переменной (в некотором смысле OP использует этот термин). Он просто создает временное значение из приведенного значения переменной, которое остается нетронутым.
Когда вы выполняете умножение a * b * d
, произойдет то, что b
и d
получат продвинутый для uint32_t
(или int
, если int
шире, чем uint32_t
), чтобы соответствовать типу a
. Однако эта операция может переполниться. Итак, что вам нужно сделать, это бросить хотя бы один из них на uint_64_t
, чтобы этого не произошло.
Обратите внимание, что (uint64_t)(a * b * d)
НЕ будет работать. Приведение типов имеет более низкий приоритет, чем круглые скобки.
Более строго тип может быть int
, если int
шире, чем uint32_t
.
@eerorika: Но выражение, состоящее из int
и uint32_t
, может быть uint32_t
.
@ Вирсавия, о да. Я предполагал два uint8_t
, что здесь не так.
@eerorika: Действительно, это тонко - одна из причин, по которой я влез с ответом.
@Bathsheba Вы уверены, что он будет повышен до подписал int?
@Broman: Да, например, тип unsigned char * unsigned char
— это int
.
b
не повышается до uint32_t
. целые акции сделает это максимум int
. После целочисленного продвижения обычные арифметические преобразования преобразует его в тип a
, если a
шире, чем int
.
a * b * d
является выражением типа uint32_t
или int
, если int
шире, чем uint32_t
(из-за правила преобразования для uint8_t
).
Тот факт, что это выражение присвоено более широкому типу, не имеет значения. Это суть вопроса.
Написание c = 1ULL * a * b * d
— это легкое исправление.
На самом деле, я думаю, что суть проблемы — это убеждение или гипотеза ОП о том, что на оценку влияет цель задания. Заявление о том, что оценка выражения определяется операндами и не зависит от цели возможного присваивания, поставит их на правильный путь.
@EricPostpischil: это то, что я хотел сказать, но, по-видимому, это недостаточно ясно.
В C оценка выражения определяется оператором и его операндами, а не тем, где в конечном итоге будет сохранен результат.
Выражение a * b * d
структурировано как (a * b) * d
. Итак, вычисляется a * b
, а затем результат умножается на d
.
Одно из правил для *
есть в C 2018 6.5.5 3:
The usual arithmetic conversions are performed on the operands.
обычные арифметические преобразования определены в 6.3.1.8 1. Они немного сложны, и я даю большую часть деталей ниже. Применяя их к вашему примеру:
a * b
a
— это uint32_t
, а b
— это uint8_t
.b
в int
— по сути, вся арифметика в C
выполняется с шириной не менее int
.int
составляет 32 бита или меньше, a
остается uint32_t
. В противном случае a
преобразуется в int
.a
и b
оба являются int
, преобразования выполняются и выполняется умножение.a
равен uint32_t
, b
преобразуется в uint32_t
и выполняется умножение.d
.Таким образом, если int
составляет 32 бита или меньше, умножение выполняется с uint32_t
, и результат равен uint32_t
. Если int
шире, умножение выполняется на int
, и результат равен int
.
Приведение любого операнда к uint64_t
приведет к тому, что арифметика будет выполняться с uint64_t
. (За исключением того, что теоретически возможно, что int
шире, чем uint64_t
, и в этом случае арифметика будет выполняться с int
, но это все же удовлетворительно - выполнение приведения гарантирует, что арифметика будет выполнена по крайней мере с этой шириной.)
Для действительных чисел обычные арифметические преобразования в основном таковы:
long double
, другой преобразуется в long double
.double
, другой преобразуется в double
.float
, другой преобразуется в float
.Целочисленные продвижения определены в 6.3.1.1 2. Они применяются ко всем целочисленным типам такой же ширины, как int
или unsigned int
(технически с рангом меньше или равным рангу int
и unsigned int
), включая битовые поля типа _Bool
, int
, signed int
или unsigned int
.
If an
int
can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to anint
; otherwise, it is converted to anunsigned int
. These are called the integer promotions. All other types are unchanged by the integer promotions.
Лучший ответ, который я видел за всю неделю.
Это большой объем информации, и мне потребовалось время, чтобы понять, но я очень благодарен за помощь. Первоначально моей целью было сэкономить место в среде 8-битного микропроцессора, поэтому я пытался использовать как можно более узкие переменные, которые могут хранить их значение. В дальнейшем учту вышеизложенные советы.
вы должны прочитать о
implicit arithmetic conversions
. en.cppreference.com/w/c/language/…