Преобразование из подписанного символа в неподписанный и обратно? - PullRequest
54 голосов
/ 18 февраля 2011

Я работаю с 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 ]

104 голосов
/ 18 февраля 2011

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

Есть две вещи, которые вы можете иметь в виду, говоря, что преобразование из подписанного в неподписанное означает, что вы хотите, чтобы переменная без знака содержала значение переменной со знаком по модулю максимального значения вашего типа без знака + 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 не будет -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;
2 голосов
/ 18 февраля 2011

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

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

1 голос
/ 18 февраля 2011

Понимаете ли вы, что CLAMP255 возвращает 0 для v <0 и 255 для v> = 0?
ИМХО, CLAMP255 должен быть определен как:

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

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

0 голосов
/ 18 февраля 2011

Я не уверен на 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);
0 голосов
/ 18 февраля 2011

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...