Следующий (C99 и более новый) код хочет вычислить квадрат, ограниченный тем же числом бит, что и исходный тип фиксированной ширины.
#include <stdint.h>
uint8_t sqr8( uint8_t x) { return x*x; }
uint16_t sqr16(uint16_t x) { return x*x; }
uint32_t sqr32(uint32_t x) { return x*x; }
uint64_t sqr64(uint64_t x) { return x*x; }
Проблема заключается в следующем: в зависимости от размера int некоторые из умножений могут быть выполнены в аргументах, продвигаемых до (подписанных) int, при этом результат переполняет (подписанный) int, таким образом undefined результат в отношении стандарта; и, возможно, неправильный результат, особенно на (все реже) машинах, не использующих два дополнения.
Если int
- 32-разрядный (соответственно 16-разрядный, 64-разрядный, 80 или 128-разрядный), который встречается для sqr16
(соответственно sqr8
, sqr32
, sqr64
) когда x
составляет 0xFFFFF
(соответственно 0xFF
, 0xFFFFFFFF
, 0xFFFFFFFFFFFFFFFF
). Ни одна из 4 функций формально не переносима в C99!
Может ли C11 или более поздняя версия или какой-либо выпуск С++ исправить эту неудачную ситуацию?
Простое рабочее решение:
#include <stdint.h>
uint8_t sqr8( uint8_t x) { return 1u*x*x; }
uint16_t sqr16(uint16_t x) { return 1u*x*x; }
uint32_t sqr32(uint32_t x) { return 1u*x*x; }
uint64_t sqr64(uint64_t x) { return 1u*x*x; }
Это стандартно-совместимое, поскольку 1u
не продвигается до int
и остается без знака; таким образом, левое умножение, затем правое, выполняется как без знака, поэтому они хорошо определены, чтобы дать правильный результат в нужном количестве младших разрядов; то же самое для окончательного неявного приведения к ширине результата.
Обновлено: как предлагается в комментарии от Marc Glisse, я попробовал этот вариант с восемью компиляторами (три версии GCC для x86, начиная с 3.1, MS C/С++ 19.00, Keil ARM компилятор 5, два компилятора Cosmic для вариантов ST7, Microchip MCC18). Все они генерировали тот же самый код, что и оригинал (с оптимизациями, которые я использую в режиме выпуска для реальных проектов). Однако составители могли бы, по-видимому, генерировать худший код, чем оригинал; и у меня есть несколько других моих встроенных компиляторов, чтобы попробовать, включая некоторые 68K и PowerPC.
Какие другие варианты у нас есть, что обеспечивает разумный баланс между вероятной улучшенной производительностью, читабельностью и простотой?