Mała liczba konwersji Endian na ciąg znaków

13

Wprowadzenie

Podczas pracy z generatorem BMP (bitmapy) napotykam problem z konwersją liczb na mały łańcuch szesnastkowy Endian. Oto funkcja, którą tworzę w JavaScript - ale zastanawiam się, jak mały kod może działać podobnie

let liEnd= num => num.toString(16).padStart(8,'0').match(/../g).reverse().join``;
console.log(liEnd(304767)) // 304767 dec = 0x4a67f hex

Wyzwanie

Funkcja zapisu, która pobierze 32-bitową liczbę całkowitą bez znaku na wejściu i wygeneruje 8-cyfrowy ciąg szesnastkowy o małej kolejności endianów. Przykładowy algorytm, który wykonuje zadanie:

  • zamień numb na ciąg szesnastkowy np .: 304767 -> '4a67f'
  • dodaj zera dopełniające, aby uzyskać ciąg 8 znaków: '0004a67f'
  • podzielony ciąg na cztery 2-znakowe kawałki: '00','04','a6','7f'
  • odwrotna kolejność sztuk '7f','a6','04','00'
  • połącz elementy i zwróć w wyniku: '7fa60400'

Przykład wejścia i wyjścia

Numer wejściowy (lub ciąg z numerem dec) znajduje się po lewej stronie ->, a wyjściowy ciąg szesnastkowy po prawej stronie

2141586432 -> 0004a67f
304767     -> 7fa60400
Kamil Kiełczewski
źródło

Odpowiedzi:

7

05AB1E , 10 9 bajtów

žJ+h¦2ôRJ

Wypróbuj online!

-1 bajt inspirowany odpowiedzią Jelly.

žJ+   add 2^32 to input
h     convert to hex
¦     drop leading 1
2ô    split in groups of 2
R     reverse groups
J     and join them
dorycki
źródło
6

Python 3 , 37 bajtów

lambda n:n.to_bytes(4,"little").hex()

Wypróbuj online!

Rozwiązanie rekurencyjne oparte na arytmetyce ( 50 49 bajtów, działa również dla Python 2 ) :

f=lambda n,i=4:i*'1'and"%02x"%(n%256)+f(n>>8,i-1)

Wypróbuj online!

-1 bajt dzięki @JonathanAllan

Joel
źródło
Powiedziałbym, że prześlij rekursywną jako pozycję w Pythonie 2)
Jonathan Allan
f=lambda n,i=4:i*'1'and'%02x'%(n%256)+f(n>>8,i-1)zapisuje bajt :)
Jonathan Allan
@JonathanAllan Thanks. Nie znam wszystkich trików Python 2 i nie widzę, jak można go skrócić.
Joel
to nie działa, ale 37 nie będzie działać w p 2
Jonathan Allan
Tak. Niektóre z tych wbudowanych funkcji są przeznaczone tylko dla języka Python-3.
Joel
6

R , 54 53 bajtów

format.hexmode(scan()%/%256^(0:3)%%256%*%256^(3:0),8)

Wypróbuj online!

Każda grupa 2 znaków jest w rzeczywistości szesnastkową reprezentacją cyfry w podstawie 256. scan()%/%256^(0:3)%%256konwertuje na podstawową liczbę 256 z odwróconymi 4 cyframi, ...%*%256^(3:0)łączy je jako jedną liczbę całkowitą i format.hexmode(...,8)konwertuje tę liczbę na reprezentację szesnastkową z 8 cyframi.

Robin Ryder
źródło
5

JavaScript (ES7),  59  57 bajtów

Manipulacja ciągiem.

n=>(n+2**32).toString(16).match(/\B../g).reverse().join``

Wypróbuj online!

W jaki sposób?

Najpierw konwertujemy na szesnastkowy, aby upewnić się, że wszystkie wiodące są uwzględnione:n+2320

(304767 + 2**32).toString(16) // --> '10004a67f'

Wypróbuj online!

Używamy wyrażenia regularnego, /\B../gaby dopasować wszystkie grupy 2 cyfr, ignorując wiodącą dzięki ( granicy niebędącej słowem ).1\B

'10004a67f'.match(/\B../g) // --> [ '00', '04', 'a6', '7f' ]

Wypróbuj online!

My reverse()i join()aby uzyskać ostatni ciąg.


JavaScript (ES6), 61 bajtów

Funkcja rekurencyjna.

f=(n,k=4)=>k?[(x=n&255)>>4&&'']+x.toString(16)+f(n>>8,k-1):''

Wypróbuj online!

Arnauld
źródło
⭐ - dostaniesz gwiazdkę za miłą odpowiedź - podoba mi się to, krótkie, ale wciąż czyste i „ludzkie do powtórzenia” :)
Kamil Kiełczewski
5

C # (interaktywny kompilator Visual C #) , 54 bajty

x=>$"{(x=x>>16|x<<16)>>8&16711935|(x&16711935)<<8:x8}"

Zaoszczędź 4 bajty dzięki @PeterCordes

Wypróbuj online!

Wyjaśnienie

x=>                                                    //Lambda taking in an uint
     (x=x>>16|x<<16)                                   //Swap the first two and the last two bytes of the uint (0x7fa60400 -> 0x04007fa6)
                    >>8&16711935|(x&16711935)<<8       //Swap each pair of bytes in every group of 2 bytes (0x04007fa6 -> 0x0004a67f)
  $"{                                           :x8}"  //Format as hex string, padded with leading zeroes to length 8
Wcielenie ignorancji
źródło
Czy można zmniejszyć 4278255360stałą maskę do 16711935( 0xff00ff), jeśli przesuniesz się przed maskowaniem? Czy to kosztuje dodatkowe pareny? Ponadto, jeśli nie, to 0xff00ff00jest tej samej długości, ale o wiele bardziej znaczący dla ludzi.
Peter Cordes
@PeterCordes Ma również tę dodatkową zaletę, że może usuwać nawiasy klamrowe, ponieważ >>ma wyższy priorytet niż &, dzięki czemu zaoszczędzono łącznie 4 bajty. Dzięki!
Embodiment of Ignorance
Fajne. W sekcji „wyjaśnienia” sugeruję zapisanie stałych w systemie szesnastkowym.
Peter Cordes
4

Japt -P , 10 bajtów

sG ùT8 ò w

Spróbuj

sG ùT8 ò w     :Implicit input of integer
s              :Convert to string
 G             :  In base-16
   ù           :Left pad
    T          :  With 0
     8         :  To length 8
       ò       :Split into 2s
         w     :Reverse
               :Implicitly join and output
Kudłaty
źródło
Co ma -Pzrobić?
SS Anne
🚀 twoja odpowiedź jest na górze (możesz dodać wyjaśnienie?)
Kamil Kiełczewski,
@ JL2210 Z dokumentacji : „ -P: Jeśli dane wyjściowe są tablicą, dane wyjściowe bez separatora (tzn. Połączone z P). ”. Tak więc flaga służy do niejawnego zamiast jawnego łączenia w celu zapisania bajtów. :)
Kevin Cruijssen
2
@ KamilKiełczewski, dodano wyjaśnienie.
Kudłaty
4

C (gcc) , 30 bajtów

f(x){printf("%.8x",htonl(x));}

Wypróbuj online!

SS Anne
źródło
kiedy działa na maszynie big-endian, to nie zmieni się w little-endian?
Peter Ferrie
@peterferrie Patrz wersja 3.
SS Anne,
4

Python 2 , 43 bajty

lambda n:[("%08x"%n)[i^6]for i in range(8)]

Wypróbuj online!

-4 bajty dzięki benrg

Wyświetla listę znaków. Obliczany przez wyszukiwanie w kolejności cyfr szesnastkowych danych wejściowych przy indeksach 6, 7, 4, 5, 2, 3, 0, 1.

ujemna siódemka
źródło
2
[i^6]for i in range(8)oszczędza kilka bajtów.
benrg
Czy wolno wypisywać listę zamiast łańcucha?
Qwertiy
dane wyjściowe, ponieważ lista naprawdę nie pasuje do ducha pytania imo
qwr
3

C (gcc) endian agnostic, bez standardowych bibliotek lib, 92 91 bajtów

h(n)to jednocyfrowa funkcja pomocnicza liczby szesnastkowej.
f(x,p)przyjmuje liczbę całkowitą i char[8]wskaźnik. Wynik to 8 bajtów chardanych. ( Nie kończy się na 0, chyba że dzwoniący to zrobi.)

Założenia: zestaw znaków ASCII. Uzupełnienie 2, intwięc prawe przesunięcie w końcu obniża bit znaku, a konwersja uint32_tna intnie nie przerywa wzoru bitowego, jeśli ustawiony jest wysoki bit. intjest co najmniej 32-bitowy. (Szerszy może pozwolić, aby działał na uzupełnieniach 1 lub implementacjach C o sile znaku).

Brak założeń: wszystko o bajtowej kolejności realizacji lub podpisaniu char.

i;h(n){n&=15;return n>9?n+87:n+48;}f(x,p)char*p;{for(i=5;--i;x>>=8)*p++=h(x>>4),*p++=h(x);}

Wypróbuj online! w tym testujący dzwoniącego używający printf("%.8s\n", buf)do wydrukowania bufora wyjściowego bez zerowania go.

Nie golfowany:

int h(n){n&=15;return n>9 ? n+'a'-10 : n+'0';}      // single digit integer -> hex

int i;
void ungolfed_f(x,p)char*p;{
    for(i=5; --i; x>>=8)   // LS byte first across bytes
        *p++=h(x>>4),      // MS nibble first within bytes
        *p++=h(x);
}

Robienie w n&=15;środku h(x)jest progiem rentowności; 6 bajtów tam w porównaniu do 3 dla &15izolowania niskiego skubania w obu witrynach wywoławczych.

,jest punktem sekwencyjnym (lub równoważnym we współczesnej terminologii), więc można bezpiecznie zrobić *p++= stuffdwa razy w jednym wyrażeniu, gdy zostanie rozdzielone przez ,operatora.

>>na liczbach całkowitych ze znakiem jest implementowana jako arytmetyczna lub logiczna. GNU C definiuje to jako uzupełnienie arytmetyki 2. Ale na maszynie dopełniającej 2 nie ma to tak naprawdę znaczenia, ponieważ nigdy nie patrzymy na przesunięte 0 lub kopie bitu znaku. Oryginalny MSB ostatecznie przejdzie do niskiego bajtu bez zmian. Nie dotyczy to znaku / wielkości i nie jestem pewien co do uzupełnienia 1.

Może to być więc przenośne tylko dla implementacji C uzupełnienia 2. (Lub gdy intjest szersza niż 32 bity więc bit 31 jest tylko częścią tej wielkości.) Unsigned -> podpisana konwersji również munges bit-wzorzec dla ujemnych liczb całkowitych, więc &15na zasadzie intbyłoby wyodrębnić tylko przekąski pierwotnej wartości bez znaku na 2 za uzupełnienie. Ponownie, chyba że intbył szerszy niż 32-bitowy, więc wszystkie wejścia są nieujemne.

Wersja golfowa ma UB od upadku z końca funkcji nieważności. Nie zwracać wartości, tylko po to, aby uniknąć deklarowania jej voidzamiast wartości domyślnej int. Nowoczesne kompilatory zepsują to przy włączonej optymalizacji.


Motywacja: Zastanawiałem się nad odpowiedzią ASM x86 lub ARM Thumb, pomyślałem, że fajnie byłoby to zrobić ręcznie w C, być może dla asm wygenerowanego przez kompilator jako punkt wyjścia. Zobacz /programming/53823756/how-to-convert-a-number-to-hex, aby uzyskać energooszczędny system x86 asm, w tym wersję AVX512VBMI, która zawiera tylko 2 instrukcje (ale potrzebuje wektorów kontrolnych dla vpmultishiftqb i vpshufb więc nie byłoby świetnie do golfa). Zwykle SIMD wymaga dodatkowej pracy, aby odwrócić bajt do kolejności drukowania na little-endian x86, więc to wyjście w postaci odwróconego bajtu jest w rzeczywistości łatwiejsze niż normalnie.


Inne pomysły

Zastanawiałem się nad pobraniem liczby całkowitej przez odwołanie i zapętlenie jej bajtów char*na implementacji C-endian (takiej jak x86 lub ARM). Ale nie sądzę, by to wiele zaoszczędziło.

Używanie sprintfdo zrobienia 1 bajtu naraz, 64 bajty po grze w golfa:

int i;
void f(x,p)char*p;{
        for(i=4;sprintf(p,"%.2x",x&255),--i;x>>=8)
                p+=2;
}

Ale jeśli korzystamy z funkcji podobnych do printf, równie dobrze moglibyśmy zamieniać bajty i robić %xprintf całej rzeczy, takiej jak odpowiedź @ JL2210 .

Peter Cordes
źródło
⭐ - dostajesz gwiazdkę za miłą odpowiedź
Kamil Kiełczewski
3

Kod maszynowy SIM86 x86 (AVX512-VBMI), 36 bajtów

(16 bajtów, które są tabelą wyszukiwania szesnastkowego)

Jest to funkcja, która przyjmuje liczbę całkowitą xmm0i zwraca 8 bajtów danych znakowych ASCII xmm0, aby osoba dzwoniąca zapisywała gdziekolwiek chce. (np. do pamięci wideo po przeplataniu bajtami atrybutów, w ciąg znaków w budowie lub cokolwiek innego)

Od C, nazwij to jak __m128i retval = lehex(_mm_cvtsi32_si128(x))w konwencji wywoływania Systemu V x86-64 lub MS Windows vectorcall.

# disassembly with machine-code bytes (the answer) and NASM source code.
0000000000401000 <lehex>:
  401000:       c5 f1 72 d0 04          vpsrld      xmm1, xmm0, 4         ; AVX1
  401005:       c5 f1 60 c8             vpunpcklbw  xmm1, xmm1, xmm0      ; AVX1
  401009:    62 f2 75 08 8d 05 01 00 00 00 vpermb  xmm0, xmm1, [rel .hex_lut]
  401013:       c3                      ret    

0000000000401014 <lehex.hex_lut>:
  401014:     30 31 ...  61 62 ...     .hex_lut:  db "0123456789abcdef"

Razem = 0x24 = 36 bajtów.

Zobacz Jak przekonwertować liczbę na heksadecymalną? na SO, jak to działa. (SSE2 dla shift / punpck, a następnie vpermboszczędza pracę, której potrzebowalibyśmy pshufb. AVX1 zamiast SSE2 / SSSE3 również unika movapskopiowania rejestru.)

Zauważ, że punpcklbwprzy operandach źródłowych w tej kolejności otrzymamy najbardziej znaczący skrawek niskiego bajtu wejściowego w elemencie najniższego bajtu, a następnie najmniej znaczący skrawek najniższego bajtu źródłowego. (W tej odpowiedzi SO, bswapna wejściu użyto znaku a, aby uzyskać wynik w standardowej kolejności drukowania tylko z SSE2. Ale tutaj potrzebujemy tej kolejności: wysokiego skubania w dolnym elemencie w każdym bajcie, ale wciąż małej kolejności bajtów endian).

Gdybyśmy mieli więcej stałych danych, moglibyśmy zaoszczędzić miejsce w trybie adresowania, wykonując jeden z nich, mov edx, imm32a następnie używając innych [rdx+16]trybów adresowania. Lub vpbroadcastb xmm0, [rdx+1].

Ale myślę, że 16-bajtowy hex LUT + vpermbjest wciąż lepszy niż implementacja n>9 : n+'a'-10 : n+'0'warunku: wymaga 3 stałych i co najmniej 3 instrukcji z maskowaniem bajtów AVX512BW (porównaj do maski, vpaddbmaskowania vpaddbscalonego) lub więcej z AVX1 lub SSE2. (Zobacz Jak przekonwertować liczbę na heksadecymalną na SO dla tej wersji SSE2). Każda instrukcja AVX512BW ma długość co najmniej 6 bajtów (4-bajtowy EVEX + kod operacyjny + modrm), dłużej z przesunięciem w trybie adresowania.

Właściwie zajęłoby to co najmniej 4 instrukcje, ponieważ przed porównaniem musimy wyczyścić wysokie śmieci za pomocą andps((lub EVEX vpanddz 4-bajtowym operandem pamięci rozgłoszeniowej). I każda z nich potrzebuje innej stałej wektorowej. AVX512 ma operandy pamięci rozgłoszeniowej, ale tylko dla elementów 32-bitowych i szerszych. np. ostatni operand EVEXvpaddb jest tylko xmm3/m128, a nie xmm3/m128/m8bcst. (Porty ładowania Intela mogą wykonywać 32-bitowe i 64-bitowe transmisje za darmo jako część pakietu ładowania, dlatego Intel zaprojektował AVX512BW, aby to odzwierciedlić i nie był w stanie zakodować operandów pamięci bajtów lub słów, zamiast dać im opcję wykonaj transmisje dword, abyś mógł nadal kompresować swoje stałe do 4 bajtów: /.)

Powód, dla którego użyłem AVX512VBMIvpermb zamiast SSSE3 / AVX1 pshufbjest dwojaki:

  • vpermbignoruje wysokie bity selektorów. (v)pshufbzera bajtów zgodnie z wysokim bitem wektora kontrolnego i potrzebowałby dodatkowego pandlub andpsfaktycznie izolowałby skubki. Przy rozmiarze XMM / 16 bajtów, vpermbpatrzy tylko na 4 małe bity elementów sterujących tasowaniem, tj. Bity [3:0]w notacji Intela w sekcji Operacja .
  • vpermbmoże potrwać dane do przetasowania (tabela odnośników) jako operand pamięci. (v)pshufbOperand xmm / mem jest wektorem kontrolującym losowanie.

Pamiętaj, że AVX512VBMI jest dostępny tylko na CannonLake / Ice Lake, więc prawdopodobnie potrzebujesz symulatora, aby to przetestować, na przykład SDE Intela.

Peter Cordes
źródło
⭐ - dostajesz gwiazdkę za miłą odpowiedź
Kamil Kiełczewski
@ KamilKiełczewski: lol dzięki. Efektywne przekształcanie liczb na hex jest jedną z moich ulubionych rzeczy. To dobry przypadek użycia dla kilku schludnych sztuczek i manipulacji bitami.
Peter Cordes
3

Scala , 58 40 36 bajtów

"%08X"format Integer.reverseBytes(_)

Wypróbuj online!

Nadal używa wbudowanego do odwracania bajtów Int, ale używa formatdo formatowania Intjako Hex. Nie musisz dzwonić toHexString.

Usunięto pareny na format. Oznacza to teraz, że argument można przyjąć domyślnie za pomocą _.

Mydlany
źródło
2

Dalej (gforth) , 52 51 40 bajtów

: f hex 0 4. do <# # # 0. #> type loop ;

Wypróbuj online!

Wyjaśnienie kodu

: f           \ start a new word definition
  hex         \ set the current base to base 16
  0           \ convert the input number to a double-cell integer
  4. do       \ start a counted loop from 0 to 3
    <# # #    \ start a formatted numeric string and move last 2 digits to format area
    0.        \ move remaining digits down the stack
    #>        \ delete top two stack value and convert format area to string
    type      \ output string
  loop        \ end loop
;             \ end word definition
reffu
źródło
2

Galaretka , 13 bajtów

+Ø%b⁴Ḋs2Ṛ‘ịØh

Wypróbuj online!

Pełny program, który jako argument przyjmuje liczbę całkowitą i wypisuje ciąg.

Nick Kennedy
źródło
🚀 twoja odpowiedź jest na górze
Kamil Kiełczewski
2

Excel, 91 bajtów

=RIGHT(DEC2HEX(A1,8),2)&MID(DEC2HEX(A1,8),5,2)&MID(DEC2HEX(A1,8),3,2)&LEFT(DEC2HEX(A1,8),2)
Wernisch
źródło
2

K4 , 12 11 bajtów

Rozwiązanie:

,/$|4_0x0\:

Przykłady:

q)k),/$|4_0x0\:304767
"7fa60400"
q)0W
"0004a67f"

Wyjaśnienie:

Prawie dokładnie to, o co pyta pytanie:

,/$|4_0x0\: / the solution
      0x0\: / split to bytes
    4_      / drop first 4 bytes
   |        / reverse
  $         / convert to string
,/          / flatten

Uwagi:

  • -1 bajt, ponieważ liczby K4 są domyślnie długie (64-bitowe), więc usunięcie 4 bajtów (32-bitowych)
streetster
źródło
🚀 twoja odpowiedź jest na górze
Kamil Kiełczewski
2

PHP , 31 bajtów

<?=unpack(H8,pack(V,$argn))[1];

Wypróbuj online!

Korzystając z pakietu PHP i rozpakuj , pakuję niepodpisane dane wejściowe w formacie „32 bit little endian byte order” ( V) w ciąg binarny, a następnie rozpakowuję go w formacie „hex hex, najpierw high nibble najpierw” ( H) i wypisuję wynik.

Wydaje się, że jest to jeden z rzadkich przypadków, w których wbudowane PHP są w rzeczywistości krótsze niż implementacja prostego algorytmu!

Noc 2
źródło
PHP pack()/ unpack()funkcje są niesamowite, ponieważ 0 razy potrzebujesz ich w większości projektów PHP. Gratulacje, znalazłeś ich zastosowanie!
640 KB
1

Węgiel drzewny , 11 bajtów

⪫⮌⪪﹪%08xN²ω

Wypróbuj online! Link jest do pełnej wersji kodu. Wyjaśnienie:

        N   Input as a number
   ﹪%08x    Format using literal string
  ⪪      ²  Split into pairs of characters
 ⮌          Reverse
⪫         ω Join
            Implicitly print

19 bajtów bez uciekania się do formatowania w języku Python:

⪫…⮌⪪⍘⁺X²¦³⁶N¹⁶¦²¦⁴ω

Wypróbuj online! Link jest do pełnej wersji kodu. Wyjaśnienie:

           N        Input as a number
     ⁺              Plus
       ²            Literal 2
      X             To power
         ³⁶         Literal 36
    ⍘               Convert to base
            ¹⁶      Literal 16
   ⪪           ²    Split into pairs of digits
  ⮌                 Reverse the list
 …               ⁴  Take the first 4 pairs
⪫                 ω Join together
                    Implicitly print
Neil
źródło
🚀 twoja odpowiedź jest na górze
Kamil Kiełczewski
1

J , 10 bajtów

8{._1{3!:3

Wypróbuj online!

w jaki sposób

3!:3jest udokumentowaną tutaj „zagraniczną koniunkcją” dla reprezentacji szesnastkowej . Oznacza to, że jest wbudowanym narzędziem do konwersji na hex. Jednak nie jest to dokładnie to, czego chcemy. Np. Bieganie:

3!:3 (304767)

produkuje:

e300000000000000
0400000000000000
0100000000000000
0000000000000000
7fa6040000000000

Znaczenie innych wierszy wyjaśniono na stronie z dokumentami, do której prowadziłem powyżej. W każdym razie jest jasne, że chcemy pierwszych 8 znaków w ostatniej linii.

_1{ dostać ostatnią linię.

8{. pobiera pierwsze 8 znaków.

Jonasz
źródło
🚀 twoja odpowiedź jest na górze
Kamil Kiełczewski
1

Rubin , 31 27 bajtów

Skończyło się to odpowiedzią PHP na Night2, ponieważ Ruby ma tę samą funkcjonalność pakowania / rozpakowywania.

->*i{i.pack(?V).unpack'H8'}

Wypróbuj online!

Moja oryginalna 31-bajtowa odpowiedź, która nie korzystała z trybu rozpakowywania H8, ponieważ nie wiedziałam o tym:

->*i{'%02x'*4%i.pack(?V).bytes}

Wypróbuj online!

Wartość tuszu
źródło
1

Pakiet Windows, 90 bajtów

@for /l %%x in (24,-8,0)do @set/aa=%1^>^>%%x^&255&cmd/cexit !a!&<nul set/p=!=exitcode:~-2!

Uruchom wiersz poleceń za pomocą / v, aby włączyć opóźnione rozszerzenie.

Peter Ferrie
źródło
1

x86 32-bitowy kod maszynowy, 24 21 bajtów

dziennik zmian: -3 bajty: zamień standardowy add / cmp / jbe / add na hack DAS przez @peter ferrie

64-bit: nadal 24 bajty. Tryb długi usunął kod operacyjny DAS.
Tryb 16-bitowy: domyślny rozmiar operandu to 16-bit, ale specyfikacja problemu jest z natury 32-bitowa. Łącznie z zakodowanymi 8 cyframi szesnastkowymi.


bswapOdwracanie bajtów, a następnie ręczne int-> hex w standardowej kolejności (najpierw najbardziej znaczące skubanie, zapisywanie cyfr szesnastkowych w buforze wyjściowym char w porządku rosnącym). Pozwala to uniknąć konieczności rozwijania pętli w celu przełączania kolejności między skubkami w bajcie vs. przez bajty.

Można wywoływać void lehex(char buf[8] /*edi*/, uint32_t x /*esi*/);jak w systemie x86-64 System V, ale nie działa to w trybie 64-bitowym. (Potrzebuje wskaźnika wyjściowego w EDI dla stosb. Numer wejściowy może znajdować się w dowolnym rejestrze innym niż ECX lub EAX.)

     1                             lehex:
     2 00000000 0FCE                   bswap  esi
     3 00000002 6A08                   push   8            ; 8 hex digits
     4 00000004 59                     pop    ecx
     5                             .loop:                ;do{
     6 00000005 C1C604                 rol    esi, 4       ; rotate high nibble to the bottom
     7                             
     8 00000008 89F0                   mov    eax, esi
     9 0000000A 240F                   and    al, 0x0f     ; isolate low nibble
    10 0000000C 3C0A                   cmp al, 10          ; set CF according to digit <= 9
    11 0000000E 1C69                   sbb al, 0x69        ; read CF, set CF and conditionally set AF
    12 00000010 2F                     das                 ; magic, which happens to work
    13                             
    14 00000011 AA                     stosb               ; *edi++ = al
    15 00000012 E2F1                   loop  .loop       ; }while(--ecx)
    16                             
    17 00000014 C3                     ret

rozmiar = 0x15 = 21 bajtów.

32-bitowa walizka testowa TIO FASM x86 z wywołującym asm, który używa writewywołania systemowego do zapisu danych wyjściowych po dwukrotnym wywołaniu go w celu dołączenia 2 łańcuchów do bufora. Testuje wszystkie cyfry szesnastkowe 0..F, w tym 9 i A na granicy między cyfrą a literą.

DASHack - x86 ma flagę pół-carry, dla przeprowadzenia niskiej skubać. Przydatny do rzeczy z zapakowanym BCD, takich jak instrukcja DAS, przeznaczonych do użycia po odjęciu dwóch 2-cyfrowych liczb całkowitych BCD. Ponieważ niski poziom AL jest poza zakresem 0-9, zdecydowanie nadużywamy go tutaj.

Zwróć uwagę if (old_AL > 99H) or (old_CF = 1)NA TO, AL ← AL − 60H;część rozdziału Obsługa w podręczniku; sbb zawsze ustawia tutaj CF, więc ta część zawsze się dzieje. To i zakres ASCII dla wielkich liter motywuje do wyborusub al, 0x69

  • cmp 0xD, 0xA nie ustawia CF
  • sbb jest 0xD - 0x69zawijany do AL = 0xA4jako dane wejściowe do DAS. (I ustawia CF, czyści AF)
  • brak AL - = 6 w pierwszej części DAS (ponieważ 4> 9 jest fałszem, a AF = 0)
  • AL - = 0x60 w drugiej części, pozostawiając 0x44kod ASCII dla'D'

kontra cyfra:

  • cmp 0x3, 0xA ustawia CF
  • sbb 3 - 0x69 - 1= AL = 0x99 i ustawia CF i AF
  • brak AL - = 6 w pierwszej części DAS (9> 9 jest fałszem, ale AF jest ustawiony), pozostawiając 0x93
  • AL - = 0x60 w drugiej części, pozostawiając 0x33, kod ASCII dla '3'.

Odejmowanie 0x6aw SBB spowoduje ustawienie AF dla każdej cyfry <= 9, aby wszystkie cyfry były zgodne z tą samą logiką. I pozostaw to wyczyszczone dla każdej alfabetycznej cyfry szesnastkowej. tj. prawidłowe wykorzystanie dzielonej obsługi 9 / A DAS.


Zwykle (dla wydajności) użyłbyś tabeli przeglądowej dla pętli skalarnej lub ewentualnie bez rozgałęzienia 2x leai cmp/cmovdodawania warunkowego. Ale al, imm8instrukcje 2-bajtowe to duża wygrana dla rozmiaru kodu.


Wersja w wersji x86-64 : tylko inna część, pomiędzy and al, 0xfi stosb.

;; x86-64 int -> hex  in 8 bytes
    10 0000000C 0430                   add    al, '0'
    11 0000000E 3C39                   cmp    al, '9'
    12 00000010 7602                   jbe  .digit
    13 00000012 0427                     add    al, 'a'-10 - '0'     ; al =  al>9 ? al+'a'-10 : al+'0'
    14                             .digit:

Zauważ, że add al, '0' zawsze działa, a dodawanie warunkowe dodaje tylko różnicę między 'a'-10i '0', aby było to po prostu ifzamiast if/ else.

Testowany i działa, używając tego samego mainwywołującego co moja odpowiedź C , który używa char buf[8]i printf("%.8s\n", buf).

Peter Cordes
źródło
czy możesz utworzyć fragment kodu online np. tutaj ?
Kamil Kiełczewski,
@ KamilKiełczewski: TIO uniemożliwia (AFAIK) napisanie programu wywołującego w C w celu przetestowania funkcji asm, więc często nie zawracam sobie głowy, ale pewnie, ponieważ zapytałeś i sys_writemogę łatwo wypisać ciągi o stałej długości. Och, ciekawe, nie zdałem sobie sprawy, że FASM na TIO pozwala tworzyć 32-bitowe pliki wykonywalne, w przeciwieństwie do NASM, w którym nie szanuje -felf32. W każdym razie wolę x86-64, a ta odpowiedź nie zapisuje żadnych bajtów z kodu 32-bitowego.
Peter Cordes
⭐ - dostajesz gwiazdkę za miłą odpowiedź
Kamil Kiełczewski
1
@ JL2210: Masz na myśli sprintf? Nie sądzę, że libc ma jakieś przydatne funkcje int-> string inne niż oparte na format-string, tylko string-> int jak strtoul. Ale tak, bswap / printf byłby prawdopodobnie krótszy, gdybyś mógł znaleźć jakiś sposób na policzenie bajtów dla wpisu GOT dla funkcji w bibliotece dynamicznej (oprócz 6-bajtowej call [rel printf wrt ..got]witryny wywoływania); minimalne statycznie powiązane pliki wykonywalne mogą być znacznie mniejsze niż dynamiczne, przynajmniej jeśli są tworzone ldprzy normalnych ustawieniach domyślnych. Ale nie sądzę, że rozsądne byłoby statyczne łączenie go, ale nie liczenie jego rozmiaru kodu.
Peter Cordes
1
@ JL2210: Pamiętaj, że jest to odpowiedź na kod maszynowy x86 , a nie rozmiar źródła tekstu asm. Nie korzystałem z funkcji libc w poprzednich odpowiedziach na kod maszynowy, tylko wywołania systemowe dla Linuksa (np. W Fibonacciego) i IDK, jak poszedłbym o policzenie kosztów lub czy w ogóle chciałbym pisać odpowiedzi na kod maszynowy z libc . Istnieją przypadki użycia dla kodu maszynowego x86, w których libc nie jest dostępny, np. W bootloaderze.
Peter Cordes