Преобразование из подписанного символа в неподписанный символ и обратно?
Я работаю с JNI и имею массив типа jbyte, где jbyte представлен в виде знака со знаком, то есть в диапазоне от -128 до 127. Jbytes представляют пиксели изображения. Для обработки изображений мы обычно хотим, чтобы пиксельные компоненты находились в диапазоне от 0 до 255. Поэтому я хочу преобразовать значение jbyte в диапазон от 0 до 255 (т. Е. В тот же диапазон, что и беззнаковый символ), выполнить некоторые вычисления для значения и затем сохранить результат снова как jbyte.
Как я могу сделать это преобразование безопасно?
Мне удалось заставить этот код работать, где значение пикселя увеличивается на 30, но ограничивается значением 255, но я не понимаю, безопасно это или переносимо:
#define CLAMP255(v) (v > 255 ? 255 : (v < 0 ? 0 : v))
jbyte pixel = ...
pixel = CLAMP_255((unsigned char)pixel + 30);
Мне интересно знать, как это сделать на C и C++.
5 ответов
Это одна из причин, почему C++ представил новый стиль приведения, который включает static_cast
а также reinterpret_cast
Есть две вещи, которые вы можете иметь в виду, говоря, что преобразование из подписанного в неподписанное означает, что вы хотите, чтобы переменная без знака содержала значение переменной со знаком по модулю максимального значения вашего типа без знака + 1. То есть, если ваш подписанный символ имеет значение -128 тогда CHAR_MAX+1
добавляется для значения 128, и если оно имеет значение -1, то CHAR_MAX+1
добавляется для значения 255, это то, что делает static_cast. С другой стороны, вы можете интерпретировать битовое значение памяти, на которое ссылается какая-либо переменная, для интерпретации как байт без знака, независимо от целочисленного представления со знаком, используемого в системе, т. Е. Если оно имеет битовое значение 0b10000000
значение должно быть 128, а 255 - битовое значение 0b11111111
это достигается с помощью reinterpret_cast.
Теперь, для представления дополнения этих двух, это происходит точно так же, так как -128 представляется как 0b10000000
и -1 представлен как 0b11111111
и так же для всех между. Однако другие компьютеры (обычно более старые архитектуры) могут использовать другое представление со знаком, такое как знак и величина или дополнение. В дополнение к 0b10000000
bitvalue будет не -128, а -127, поэтому статическое приведение к unsigned char сделает это 129, тогда как reinterpret_cast сделает это 128. Дополнительно в дополнение к 0b11111111
bitvalue будет не -1, а -0 (да, это значение существует в дополнении к нему) и будет преобразовано в значение 0 с помощью static_cast, но в значение 255 с reinterpret_cast. Обратите внимание, что в случае дополнения единиц значение без знака 128 фактически не может быть представлено в знаке со знаком, поскольку оно варьируется от -127 до 127 из-за значения -0.
Я должен сказать, что подавляющее большинство компьютеров будет использовать двоичное дополнение делает весь выпуск спорным для почти в любом месте вашего кода будет когда-либо бежать. Скорее всего, вы когда-нибудь увидите системы с чем-то кроме дополнения двух в очень старых архитектурах, подумайте, временные рамки 60-х годов.
Синтаксис сводится к следующему:
signed char x = -100;
unsigned char y;
y = (unsigned char)x; // C static
y = *(unsigned char*)(&x); // C reinterpret
y = static_cast<unsigned char>(x); // C++ static
y = reinterpret_cast<unsigned char&>(x); // C++ reinterpret
Чтобы сделать это хорошим способом C++ с массивами:
jbyte memory_buffer[nr_pixels];
unsigned char* pixels = reinterpret_cast<unsigned char*>(memory_buffer);
или путь C:
unsigned char* pixels = (unsigned char*)memory_buffer;
Да, это безопасно.
В языке c используется функция, называемая целочисленным продвижением, для увеличения числа битов в значении перед выполнением вычислений. Поэтому ваш макрос CLAMP255 будет работать с целочисленной (вероятно, 32-битной) точностью. Результат присваивается jbyte, что уменьшает целочисленную точность до 8 бит, вписывающихся в jbyte.
Вы понимаете, что CLAMP255 возвращает 0 для v < 0 и 255 для v >= 0?
ИМХО, CLAMP255 должен быть определен как:
#define CLAMP255(v) (v > 255 ? 255 : (v < 0 ? 0 : v))
Разница: если v не больше 255 и не меньше 0: вернуть v вместо 255
Есть два способа интерпретации входных данных; либо -128 - это самое низкое значение, а 127 - самое высокое (т. е. истинно подписанные данные), либо 0 - самое низкое значение, 127 - где-то посередине, а следующее "более высокое" число - -128, где -1 - это "самое высокое" значение (то есть, самый старший бит уже был неверно истолкован как знаковый бит в двоичной записи дополнения).
Предполагая, что вы имеете в виду последнее, формально правильный путь
signed char in = ...
unsigned char out = (in < 0)?(in + 256):in;
который, по крайней мере, gcc правильно распознает как no-op.
Я не уверен на 100%, что понимаю ваш вопрос, поэтому скажите мне, если я ошибаюсь.
Если я правильно понял, вы читаете jbytes, которые являются технически подписанными символами, но в действительности имеют значения пикселей в диапазоне от 0 до 255, и вы задаетесь вопросом, как вам следует обращаться с ними, не повреждая значения в процессе.
Затем вы должны сделать следующее:
конвертируйте jbytes в unsigned char, прежде чем делать что-либо еще, это определенно восстановит значения пикселей, которыми вы пытаетесь манипулировать
используйте больший целочисленный тип со знаком, такой как int, при выполнении промежуточных вычислений, чтобы убедиться, что переполнение и переполнение могут быть обнаружены и устранены (в частности, не приведение к знаковому типу может заставить компилятор преобразовывать каждый тип в беззнаковый тип, и в этом случае вы не сможете обнаружить потери в дальнейшем)
при возврате в jbyte вы захотите ограничить свое значение диапазоном 0-255, преобразовать в беззнаковый символ и затем снова преобразовать в знаковый символ: я не уверен, что первое преобразование строго необходимо, но вы просто можете не ошибаюсь, если вы оба
Например:
inline int fromJByte(jbyte pixel) {
// cast to unsigned char re-interprets values as 0-255
// cast to int will make intermediate calculations safer
return static_cast<int>(static_cast<unsigned char>(pixel));
}
inline jbyte fromInt(int pixel) {
if(pixel < 0)
pixel = 0;
if(pixel > 255)
pixel = 255;
return static_cast<jbyte>(static_cast<unsigned char>(pixel));
}
jbyte in = ...
int intermediate = fromJByte(in) + 30;
jbyte out = fromInt(intermediate);