Подтвердить что ты не робот

Преобразование из подписанного char в unsigned char и обратно?

Я работаю с JNI и имею массив типа jbyte, где jbyte представлен как подписанный char i.e. от -128 до 127. jbytes представляют пиксели изображения. Для обработки изображений мы обычно хотим, чтобы компоненты пикселей находились в диапазоне от 0 до 255. Поэтому я хочу преобразовать значение jbyte в диапазон от 0 до 255 (то есть в том же диапазоне, что и без знака char), выполнить некоторые вычисления по значению, а затем снова сохраните результат как jbyte.

Как я могу сделать это преобразование безопасно?

Мне удалось заставить этот код работать, где значение пикселя увеличивается на 30, но привязывается к значению 255, но я не понимаю, безопасно ли оно или переносимо:

 #define CLAMP255(v) (v > 255 ? 255 : (v < 0 ? 0 : v))

 jbyte pixel = ...
 pixel = CLAMP_255((unsigned char)pixel + 30);

Мне интересно знать, как это сделать как на C, так и на С++.

4b9b3361

Ответ 1

Это одна из причин, почему С++ представил новый стиль литья, который включает в себя static_cast и reinterpret_cast

Есть две вещи, которые вы можете сказать, произнеся преобразование из подписанного в unsigned, вы можете означать, что вы хотите, чтобы переменная unsigned содержала значение подписанной переменной по модулю максимального значения вашего неподписанного типа + 1. То есть, если ваш подписанный char имеет значение -128, тогда CHAR_MAX+1 добавляется для значения 128, и если оно имеет значение -1, то добавляется CHAR_MAX+1 для значения 255, это то, что делается static_cast. С другой стороны, вы можете интерпретировать значение бита памяти, на которое ссылается какая-либо переменная, которая должна интерпретироваться как беззнаковый байт, независимо от знакового целочисленного представления, используемого в системе, то есть, если оно имеет значение бит 0b10000000, оно должно оценивать для значения 128 и 255 для значения бит 0b11111111, это выполняется с помощью reinterpret_cast.

Теперь для двух дополняющих представлений это происходит точно так же, поскольку -128 представляется как 0b10000000, а -1 представляется как 0b11111111 и аналогично для всех между ними. Однако другие компьютеры (обычно более старые архитектуры) могут использовать различное подписанное представление, такое как знак и величина или дополнение. В дополнение к этому бит-бит 0b10000000 не будет -128, но -127, поэтому статический приведение в unsigned char сделает это 129, в то время как reinterpret_cast сделает это 128. Кроме того, в дополнение к ним 0b11111111 bitvalue не будет равным -1, но -0, (да, это значение существует в их дополнении) и будет преобразовано в значение 0 с static_cast, но значение 255 с reinterpret_cast. Обратите внимание, что в случае дополнения к ним беззнаковое значение 128 фактически не может быть представлено в подписанном char, поскольку оно колеблется от -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

Чтобы сделать это с хорошим С++ способом с массивами:

jbyte memory_buffer[nr_pixels];
unsigned char* pixels = reinterpret_cast<unsigned char*>(memory_buffer);

или способ C:

unsigned char* pixels = (unsigned char*)memory_buffer;

Ответ 2

Да, это безопасно.

В языке c используется функция, называемая целым продвижением, для увеличения количества бит в значении перед выполнением вычислений. Поэтому ваш макрос CLAMP255 будет работать с целочисленной (возможно, 32-разрядной) точностью. Результат присваивается jbyte, который уменьшает целую точность до 8 бит, вписывающихся в jbyte.

Ответ 3

Вы понимаете, что CLAMP255 возвращает 0 для v < 0 и 255 для v >= 0?
IMHO, CLAMP255 следует определить как:

#define CLAMP255(v) (v > 255 ? 255 : (v < 0 ? 0 : v))

Разница: если v не больше 255 и не меньше 0: верните v вместо 255

Ответ 4

Существует два способа интерпретации входных данных; либо -128 является самым низким значением, а 127 является самым высоким (т.е. истинными подписанными данными), или 0 является самым низким значением, 127 находится где-то посередине, а следующее "высшее" число равно -128, причем -1 является "наивысшее" значение (т.е. наиболее значимый бит уже получил неверное истолкование как знаковый бит в двухзначной нотации.

Предполагая, что вы имеете в виду последний, формально правильный способ

signed char in = ...
unsigned char out = (in < 0)?(in + 256):in;

который по крайней мере gcc правильно распознает как no-op.

Ответ 5

Я не уверен на 100%, что я понимаю ваш вопрос, поэтому скажите мне, если я ошибаюсь.

Если я правильно понял, вы читаете jbytes, которые являются технически подписанными символами, но действительно значения пикселей от 0 до 255, и вам интересно, как вы должны их обрабатывать, не искажая значения в этом процессе.

Затем вы должны сделать следующее:

  • конвертировать jbytes в unsigned char, прежде чем делать что-либо еще, это, безусловно, приведет к восстановлению значений пикселей, которые вы пытаетесь манипулировать

  • используйте более крупный тип целочисленного знака, например, int при выполнении промежуточных вычислений, чтобы убедиться, что над-и underflows могут быть обнаружены и обработаны (в частности, не приведение к подписанному типу может привести к компиляции для продвижения каждого типа к неподписанному типу, и в этом случае вы не сможете позже обнаруживать недоработки)

  • при назначении обратно в jbyte вам нужно будет закрепить ваше значение в диапазоне 0-255, конвертировать в unsigned char и затем снова преобразовать в подписанный char: я не уверен, что первое преобразование строго необходимо, но вы просто не можете ошибаться, если вы оба выполняете

Например:

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);