Konwersja z signed char na unsigned char i z powrotem?

Pracuję z JNI i mam tablicę typu jbyte, gdzie jbyte jest reprezentowany jako znak podpisany, tzn. w zakresie od -128 do 127. Jbytes reprezentują piksele obrazu. W przypadku przetwarzania obrazu zwykle chcemy, aby komponenty pikseli mieściły się w zakresie od 0 do 255. Dlatego chcę przekonwertować wartość jbyte na zakres od 0 do 255 (tzn. ten sam zakres co unsigned char), wykonać kilka obliczeń na wartości, a następnie zapisać wynik jako jbyte ponownie.

Jak mogę bezpiecznie przeprowadzić tę konwersję?

Udało mi się uruchomić ten kod, gdzie wartość piksela jest zwiększana o 30, ale zaciśnięta do wartości 255, ale nie rozumiem, czy jest Bezpieczna czy przenośna:

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

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

Jestem ciekaw jak to zrobić zarówno w C jak i C++.

 48
Author: rbcc, 2011-02-18

5 answers

Jest to jeden z powodów, dla których C++ wprowadził nowy styl cast, który obejmuje static_cast i reinterpret_cast

Są dwie rzeczy, które możesz mieć na myśli mówiąc konwersja z signed do unsigned, możesz mieć na myśli, że chcesz, aby unsigned zmienna zawierała wartość podpisanej zmiennej modulo maksymalną wartość Twojego typu unsigned + 1. Oznacza to, że jeśli znak z podpisem ma wartość -128, to CHAR_MAX+1 jest dodawany dla wartości 128, a jeśli ma wartość -1, to CHAR_MAX+1 jest dodawany dla wartości 255, to jest to, co robi static_cast. Z drugiej strony może to oznaczać interpretację wartości bitowej pamięci, do której odnosi się jakaś zmienna, która ma być interpretowana jako niepodpisany bajt, niezależnie od podpisanej reprezentacji całkowitej używanej w systemie, tzn. jeśli ma wartość bitową 0b10000000, powinna być oceniana na wartość 128, a 255 dla wartości bitowej 0b11111111, jest to wykonywane za pomocą reinterpret_cast.

Teraz dla reprezentacji dopełniacza dwójki jest to dokładnie to samo, ponieważ -128 jest reprezentowane jako 0b10000000 i -1 jest reprezentowane jako 0b11111111 i podobnie dla wszystkich pomiędzy. Jednak inne komputery (zwykle starsze architektury) mogą używać innej reprezentacji podpisanej, takiej jak znak i wielkość lub ich dopełnienie. W uzupełnieniu ones ' a wartość 0b10000000 nie będzie równa -128, ale -127, więc statyczne rzucenie znaku unsigned oznaczałoby 129, podczas gdy reinterpret_cast oznaczałoby 128. Dodatkowo w dopełnieniu ones ' a wartość 0b11111111 bitvalue nie będzie -1, ale -0, (tak ta wartość istnieje w ones ' complement,) i zostanie przekonwertowana na wartość 0 z static_cast, ale wartość 255 z reinterpret_cast. Zauważ, że w przypadku dopełnienia ones niepodpisana wartość 128 nie może być reprezentowana w znakach podpisanych, ponieważ waha się od -127 do 127, ze względu na wartość -0.

Muszę powiedzieć, że zdecydowana większość komputerów będzie używać dopełnienia dwóch, co sprawi, że cała sprawa będzie dyskusyjna w niemal każdym miejscu, w którym twój kod kiedykolwiek będzie działał. Prawdopodobnie zobaczysz tylko systemy z czymkolwiek innym niż dwa dopełniają się w bardzo starych architekturach, myślę, że w latach 60.

Składnia sprowadza się do:

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

Aby zrobić to w ładny sposób C++ z tablicami:

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

Lub droga C:

unsigned char* pixels = (unsigned char*)memory_buffer;
 95
Author: wich,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-14 02:20:40

Tak, tu jest bezpiecznie.

Język c używa funkcji o nazwie integer promotion, aby zwiększyć liczbę bitów w wartości przed wykonaniem obliczeń. Dlatego makro CLAMP255 będzie działać z dokładnością całkowitą (prawdopodobnie 32-bitową). Wynik jest przypisany do jbyte, co zmniejsza dokładność całkowitą z powrotem do 8 bitów pasujących do jbyte.

 2
Author: qbert220,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-18 11:51:43

Czy zdajesz sobie sprawę, że CLAMP255 zwraca 0 dla V = 0?
IMHO, CLAMP255 powinien być zdefiniowany jako:

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

Różnica: jeśli v nie jest większe niż 255 i nie mniejsze niż 0: zwraca v zamiast 255

 1
Author: Daniel Hilgarth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-18 11:55:12

Istnieją dwa sposoby interpretacji danych wejściowych; albo -128 jest najniższą wartością, a 127 jest najwyższą (tzn. prawdziwe dane podpisane), albo 0 jest najniższą wartością, 127 jest gdzieś pośrodku, a następna "wyższa" liczba to -128, z -1 jest "najwyższą" wartością (to znaczy, najbardziej znaczący bit został już błędnie zinterpretowany jako bit znaku w notacji dopełniacza dwójki.

Zakładając, że masz na myśli to drugie, formalnie poprawnym sposobem jest

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

Które przynajmniej GCC prawidłowo rozpoznaje jako no-op.

 0
Author: Simon Richter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-18 12:08:33

Nie jestem w 100% pewien, czy rozumiem twoje pytanie, Więc powiedz mi, czy się mylę.

Jeśli dobrze zrozumiałem, czytasz jbytes, które są technicznie znakami podpisanymi , ale naprawdę wartości pikseli w zakresie od 0 do 255 i zastanawiasz się, jak należy je obsługiwać bez uszkodzenia wartości w procesie.

Następnie należy wykonać następujące czynności:

  • Przekonwertować jbytes na unsigned char przed zrobieniem czegokolwiek innego, to z pewnością przywróci wartości pikseli, którymi próbujesz manipulować

  • Int podczas wykonywania obliczeń pośrednich, aby upewnić się, że przepływy nad - i zaniżone mogą być wykrywane i rozwiązywane (w szczególności, nie przeniesienie do podpisanego typu może wymusić na kompilatorze promowanie każdego typu do niepodpisanego typu, w którym to przypadku nie będziesz w stanie wykryć poniżej przepływów później)

  • Podczas przypisywania z powrotem do jbyte, będziesz chciał zacisnąć swoją wartość do zakresu 0-255, przekonwertuj na unsigned char, a następnie przekonwertuj ponownie na signed char: nie jestem pewien, czy pierwsza konwersja jest bezwzględnie konieczna, ale nie możesz się mylić, jeśli wykonasz oba

Na przykład:

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
Author: ZeRemz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-18 13:22:58