Я хотел бы понять, как работает умножение и деление с использованием <stdint.h> (uint16_t, int16_t и т. д.).
Например, я предполагаю, что следующее производит uint16_t:
uint16_t * uint16_t
uint16_t / uint16_t
Так что есть риск переполнения.
Что происходит в следующем случае? :
uint32_t res = uint16_t a * uint16_t b
Дает ли продукт результат uint16_t, который затем повышается до uint32_t? Другими словами, обязательно ли в этом случае приводить два операнда uint16_t к двум uint32_t перед выполнением умножения?
Для деления нет риска переполнения, поэтому не нужно беспокоиться об этом случае.
Кроме того, что происходит, когда мы пытаемся умножить uint16_t на int16_t без явного приведения? Выполняет ли компилятор какое-либо неявное приведение (например, приведение uint16_t к int16_t и возврат результата в виде int16_t)?
Дайте мне знать, если мои вопросы неясны. Спасибо.
uint32_t res = uint16_t a * uint16_t b
лучше как uint32_t res = (uint32_t) a * b
.
@chux-ReinstateMonica, ты прав, я забыл об этом особом случае
Есть... дела. Операнды *
подвергаются целые акции, см. https://en.cppreference.com/w/c/language/conversion.
Does the product give an uint16_t result
Предположим, нормальная, обычная система — int
имеет 32-разрядную версию. На такой системеint
может содержать любое значение uint16_t
. Оба операнда повышаются до int
, прежде чем что-либо делать.
На системе с 32-битной int
: нет, дают результат int
.
that is then promoted to an uint32_t ?
Да. Результат преобразованный в uint32_t
.
Из https://en.cppreference.com/w/c/language/operator_assignment :
Assignment performs implicit conversion from the value of rhs to the type of lhs and then replaces the value in the object designated by lhs with the converted value of rhs.
Как это конвертируется? Из https://en.cppreference.com/w/c/language/conversion :
if the target type is unsigned, the value 2^b , where b is the number of bits in the target type, is repeatedly subtracted or added to the source value until the result fits in the target type. In other words, unsigned integers implement modulo arithmetic.
Результат uint16_t * uint16_t
должен быть в диапазоне uint32_t
- тогда ничего не произойдет.
is it mandatory in this case to cast the two uint16_t operands to two uint32_t before doing the multiplication?
Нет, это не обязательно. C — это язык программирования с преобразованиями и повышениями скрытый, они выполняются неявно.
Существуют «рекомендации по разработке программного обеспечения», которые «улучшают» C по соображениям безопасности и требуют явного приведения типов, в первую очередь МИСРА.
what happens when we try to multiply an uint16_t by an int16_t without explicit casting?
Они оба получают повышение до int
.
Но давайте рассмотрим кое-что интересное - int
может быть 16-битным, как на компиляторе xc8. В таких системах типы не продвигаются и остаются — int16_t
и uint16_t
. Затем из https://en.cppreference.com/w/c/language/conversion:
If the unsigned type has conversion rank greater than or equal to the rank of the signed type, then the operand with the signed type is implicitly converted to the unsigned type.
int16_t
и uint16_t
имеют одинаковый конверсионный ранг — неподписанные и подписанные одинаковые типы имеют одинаковые ранги. Итак, int16_t
будет от преобразованный до uint16_t
, а затем станет uint16_t * uint16_t
.
int16_t
будет преобразован путем «неоднократного вычитания или добавления» значения 2^16 = 65536
до тех пор, пока оно не окажется в диапазоне uint16_t
. Так, например, (uint16_t)-5
равно 65531
, потому что -5 + 65536 = 65531
.
Does the compiler performs some implicit casting
Да!
Re «Да!»: Для этого вопроса OP не дает никакого контекста, который указывал бы, что результат преобразован в int16_t
.
«uint16_t * uint16_t должен быть в диапазоне uint32_t» не подходит. uint16_t * uint16_t
риски int
перелив, уб.
@EricPostpischil это был общий вопрос, но когда я прочитал ваши объяснения, я понял, что да, это зависит от используемой архитектуры. Я использую riscv-32 бита.
C 2018 6.5.5 определяет, как ведут себя операторы умножения и делителя. В пункте 3 говорится:
The usual arithmetic conversions are performed on the operands.
6.3.1.8 1 определяет обычные арифметические преобразования:
… First, if the corresponding real type of either operand is
long double
, the other operand is converted, without change of type domain [complex or real], to a type whose corresponding real type islong double
.Otherwise, if the corresponding real type of either operand is
double
, the other operand is converted, without change of type domain, to a type whose corresponding real type isdouble
.Otherwise, if the corresponding real type of either operand is
float
, the other operand is converted, without change of type domain, to a type whose corresponding real type isfloat.
Otherwise, the integer promotions are performed on both operands. Then the following rules are applied to the promoted operands:
If both operands have the same type, then no further conversion is needed.
Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank.
Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type.
Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type.
Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type.
Ранг имеет техническое определение, которое в значительной степени соответствует ширине (количеству битов в целочисленном типе).
What happens in the following case ? :
uint32_t res = uint16_t a * uint16_t b
В типичных современных реализациях C int
составляет 32 бита, поэтому он может представлять все значения uint16_t
. Таким образом, a
и b
превращаются в int
. Тогда дальнейшее преобразование не требуется. Умножение выполняется по типу int
. Это может переполниться. Например, если a
и b
равны 50 000, то произведение будет равно 2 500 000 000, но наибольшее значение, которое может представлять 32-битное int
, равно 2 147 483 647. Стандарт C не определяет поведение при переполнении. Чтобы этого избежать, следует преобразовать один или оба операнда в достаточно широкий тип, как в случае с uint32_t res = (uint32_t) a * b;
.
Предположим, что uint32_t
равно unsigned int
, целочисленные акции оставят (uint32_t) a
как unsigned int
. b
будет повышен до int
, как и раньше, но затем обычные арифметические преобразования преобразуют его в unsigned int
для третьего элемента списка, и арифметика будет выполняться с unsigned int
, и переполнения не будет.
Приведенное выше предположение int
может представлять все значения uint16_t
. Если это невозможно, то операнды не будут продвигаться целочисленными продвижениями; они останутся как тип uint16_t
. Умножение будет выполняться по типу uint16_t
. Если результат не может быть представлен, информация будет потеряна. Это не переполнение, потому что стандарт C определяет арифметику в целочисленных типах без знака для переноса по модулю 2N, где Н — количество битов в типе. Итак, поведение определено, но нормального математического результата оно не даст.
Чтобы получить желаемый результат, преобразуйте один или оба операнда в достаточно широкий тип, как и в предыдущем случае: uint32_t res = (uint32_t) a * b;
.
… is it mandatory in this case to cast the two
uint16_t
operands to twouint32_t
before doing the multiplication?
Стандарт C этого не требует.
Некоторые стандарты кодирования требуют этого.
Некоторые компиляторы могут предупреждать вас в некоторых случаях, когда они могут обнаружить проблему.
Получение правильных ответов требует приведения типов там, где желаемый математический ответ не может быть представлен в виде, полученном в результате обычных арифметических преобразований. В любом коде, где вы не можете доказать, что обычные арифметические преобразования дают тип, который может представлять все желаемые ответы, вы должны включать приведение к типу, который может.
Also, what happens when we try to multiply an
uint16_t
by anint16_t
without explicit casting?
В типичном сегодня случае с 32-битным int
оба операнда повышаются до int
, а арифметика выполняется с int
. Еще раз, вы должны привести один или оба операнда к типу, достаточно широкому для желаемого результата.
С 16-битным int
ни один операнд не продвигается. Затем обычные арифметические преобразования преобразуют их оба в uint16_t
по третьему пункту списка, а умножение выполняется с использованием uint16_t
.
Does the compiler performs some implicit casting (for example, casting the
uint16_t
to anint16_t
and returning a result as anint16_t
) ?
Существует неявное преобразование в uint16_t
, как описано выше. (Это не приведение. Слово «приведение» используется для явного оператора в исходном коде в форме (type)
. Поскольку это явный оператор, приведение никогда не является неявным. Приведения выполняют преобразования. Другие преобразования могут быть неявными. .)
В a * b
, где a
— это uint16_t
, а b
— это int16_t
, нет неявного преобразования в int16_t
. Будет преобразование или преобразования в uint16_t
или int
, в зависимости от ширины int
. Если вы присвоите результат объекту int16_t
, присвоение вызовет преобразование в int16_t
. Если присваиваемое значение не может быть представлено в int16_t
, результат (который может быть сигналом) определяется реализацией.
«Для деления нет риска переполнения» -> Однако деление на 0 — это риск.