Определение размера данных в C-программировании с использованием математических выражений - PullRequest
1 голос
/ 22 мая 2019

При использовании математических операторов в программировании на С очень важно использовать приведение типов или правильно определять размер переменной.Мне нужна помощь.

#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 бит?Есть ли какое-нибудь правило о типе знаменателя?

Ответы [ 3 ]

3 голосов
/ 22 мая 2019

Когда вы выполните умножение a * b * d, произойдет следующее: b и d получат повышение до uint32_t (или int, если int шире, чем uint32_t) для соответствия типу a. Однако эта операция может быть переполнена. Поэтому вам нужно привести хотя бы одну из них к uint_64_t, чтобы этого не произошло.

Обратите внимание, что (uint64_t)(a * b * d) НЕ будет работать. Тип приведений имеет более низкий приоритет, чем скобки.

2 голосов
/ 22 мая 2019

В C оценка выражения определяется оператором и его операндами, а не местом, где результат будет в конечном итоге сохранен.

Выражение a * b * d структурировано как (a * b) * d.Таким образом, a * b оценивается, а затем результат умножается на d.

Одно из правил для * содержится в C 2018 6.5.5 3:

Обычные арифметические преобразования выполняются над операндами.

Обычные арифметические преобразования определены в 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.

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

1 голос
/ 22 мая 2019

a * b * d является выражением типа uint32_t или int, если int шире, чем uint32_t (из-за правила преобразования для uint8_t).

Тот факт, чтоэто выражение присваивается более широкому типу не является фактором. Это суть проблемы.

Написание c = 1ULL * a * b * d исправимо.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...