Преобразование из подписанного символа в неподписанный символ и обратно?

Я работаю с 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);
Другие вопросы по тегам