Conversión de Little Endian Number a String

13

Introducción

Durante el trabajo con el generador BMP (mapa de bits) me enfrento al problema de convertir el número a una pequeña cadena hexadecimal endian. Aquí está la función que creo en JavaScript, pero me pregunto cómo puede funcionar el código pequeño de manera similar

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

Desafío

Función de escritura que tomará un número entero sin signo de 32 bits en la entrada y producirá una cadena hexadecimal de 8 dígitos con poco orden endian. El algoritmo de ejemplo que hace el trabajo:

  • convertir entumecido a cadena hexadecimal, por ejemplo: 304767 -> '4a67f'
  • agregue ceros de relleno para obtener una cadena de 8 caracteres: '0004a67f'
  • dividir la cuerda en cuatro piezas de 2 caracteres: '00','04','a6','7f'
  • orden inverso de piezas '7f','a6','04','00'
  • unir piezas y devolver como resultado: '7fa60400'

Ejemplo de entrada y salida

El número de entrada (o cadena con número dec) está a la izquierda de ->, la cadena hexadecimal de salida está a la derecha

2141586432 -> 0004a67f
304767     -> 7fa60400
Kamil Kiełczewski
fuente

Respuestas:

7

05AB1E , 10 9 bytes

žJ+h¦2ôRJ

Pruébalo en línea!

-1 byte por inspiración de la respuesta 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
dorio
fuente
6

Python 3 , 37 bytes

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

Pruébalo en línea!

Solución recursiva basada en aritmética ( 50 49 bytes, funciona también para Python 2 ) :

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

Pruébalo en línea!

-1 byte gracias a @JonathanAllan

Joel
fuente
Yo diría que envíe el recursivo como una entrada de Python 2 :)
Jonathan Allan
f=lambda n,i=4:i*'1'and'%02x'%(n%256)+f(n>>8,i-1)guarda un byte :)
Jonathan Allan
@ JonathanAllan Gracias. Sin embargo, no estoy familiarizado con todos los trucos de Python 2 y no veo cómo se puede acortar.
Joel
no lo hace, pero el 37 no funcionará en py 2
Jonathan Allan
Si. Algunos de esos incorporados son solo Python-3.
Joel
6

R , 54 53 bytes

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

Pruébalo en línea!

Cada grupo de 2 caracteres es en realidad la representación hexadecimal de un dígito en base 256. se scan()%/%256^(0:3)%%256convierte en un número base 256 con 4 dígitos invertidos, se ...%*%256^(3:0)une a ellos como un solo entero y format.hexmode(...,8)convierte ese número en su representación hexadecimal con 8 dígitos.

Robin Ryder
fuente
5

JavaScript (ES7),  59  57 bytes

Manipulación de cuerdas.

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

Pruébalo en línea!

¿Cómo?

Primero convertimos a hexadecimal para asegurarnos de que se incluyan todos los iniciales:n+2320

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

Pruébalo en línea!

Usamos la expresión regular /\B../gpara unir todos los grupos de 2 dígitos, ignorando el inicial gracias a (sin límite de palabras ).1\B

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

Pruébalo en línea!

Nosotros reverse()y join()para obtener la cadena final.


JavaScript (ES6), 61 bytes

Función recursiva.

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

Pruébalo en línea!

Arnauld
fuente
⭐ - obtienes una estrella por una buena respuesta - Me gusta, corto pero todavía limpio y "humano-legible" :)
Kamil Kiełczewski
5

C # (compilador interactivo de Visual C #) , 54 bytes

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

Guardado 4 bytes gracias a @PeterCordes

Pruébalo en línea!

Explicación

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
Encarnación de la ignorancia
fuente
¿Puede reducir la 4278255360constante de la máscara a 16711935( 0xff00ff) si cambia antes de enmascarar? ¿O eso cuesta padres adicionales? Además, si no, 0xff00ff00es de la misma longitud pero mucho más significativo para los humanos.
Peter Cordes
@PeterCordes También tiene la ventaja adicional de poder eliminar los corchetes, ya que >>tiene mayor prioridad que &, lo que ahorró un total de 4 bytes. ¡Gracias!
Encarnación de la ignorancia el
Frio. En su sección de "explicación", sugeriría escribir las constantes en hexadecimal.
Peter Cordes el
4

Japt -P , 10 bytes

sG ùT8 ò w

Intentalo

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
Lanudo
fuente
¿Qué -Phacer?
SS Anne
🚀 su respuesta está en la parte superior (¿puede agregar una explicación?)
Kamil Kiełczewski
@ JL2210 De los documentos : " -P: si la salida es una matriz, las salidas sin separador (es decir, unidas con P) ". Por lo tanto, el indicador es para una unión implícita en lugar de explícita para guardar bytes. :)
Kevin Cruijssen
2
@ KamilKiełczewski, explicación agregada.
Shaggy
4

C (gcc) , 30 bytes

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

Pruébalo en línea!

SS Anne
fuente
cuando se ejecuta en una máquina big-endian, ¿esto no se convertirá en little-endian?
Peter Ferrie
@peterferrie Ver revisión 3.
SS Anne
4

Python 2 , 43 bytes

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

Pruébalo en línea!

-4 bytes gracias a benrg

Emite una lista de caracteres. Calculado recuperando, en orden, los dígitos hexadecimales de la entrada en los índices 6, 7, 4, 5, 2, 3, 0, 1.

siete negativo
fuente
2
[i^6]for i in range(8)Guarda algunos bytes.
benrg
¿Está permitido generar una lista en lugar de una cadena?
Qwertiy
la salida como lista no es realmente adecuada para el espíritu de la pregunta imo
qwr
3

C (gcc) endian agnóstico, sin bibliotecas estándar, 92 91 bytes

h(n)es un entero de un solo dígito-> función auxiliar hexadecimal.
f(x,p)toma un entero y un char[8]puntero. El resultado son 8 bytes de chardatos. ( No termina en 0 a menos que la persona que llama lo haga).

Suposiciones: conjunto de caracteres ASCII. El complemento de 2, por intlo que el desplazamiento a la derecha finalmente reduce el bit de signo, y la conversión de uint32_ta intno modifica el patrón de bits si se establece el bit alto. intes de al menos 32 bits. (Wider podría permitir que funcione en complementos de 1 o implementaciones de magnitud de signo C).

No supuestos: cualquier cosa sobre el orden de bytes de implementación o la firma de 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);}

Pruébalo en línea! incluido el llamador de prueba que se utiliza printf("%.8s\n", buf)para imprimir el búfer de salida sin terminarlo en 0.

Sin golf:

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

Hacer n&=15;adentro h(x)es un punto de equilibrio; 6 bytes allí frente a 3 cada uno para &15aislar el mordisco bajo en ambos sitios de llamadas.

,es un punto de secuencia (o equivalente en la terminología moderna), por lo que es seguro hacer *p++= stuffdos veces en una declaración cuando está separado por el ,operador.

>>en entero con signo se define en la implementación como aritmética o lógica. GNU C lo define como el complemento aritmético 2. Pero en cualquier máquina de complemento de 2 realmente no importa porque nunca miramos los 0 desplazados o las copias del bit de signo. El MSB original eventualmente bajará al byte bajo sin cambios. Este no es el caso en signo / magnitud, y no estoy seguro sobre el complemento de 1.

Por lo tanto, esto solo puede ser portátil para las implementaciones C del complemento 2. (O, donde intes más ancho de 32 bits de modo bit 31 es sólo una parte de la magnitud.) Sin firmar -> conversión firmado también munges el patrón de bits para los números enteros negativos, por lo que &15en una intúnica extraería mordiscos del valor sin signo original en complemento a 2. Una vez más, a no ser que intera más amplia que la de 32 bits para todas las entradas son no negativas.

La versión de golf tiene UB por caerse del final de una función no nula. No devolver un valor, solo para evitar declararlo en voidlugar del valor predeterminado int. Los compiladores modernos romperán esto con la optimización habilitada.


Motivación: estaba considerando una respuesta x86 o ARM Thumb asm, pensé que podría ser divertido hacerlo manualmente en C, tal vez para el asm generado por el compilador como punto de partida. Consulte /programming/53823756/how-to-convert-a-number-to-hex para obtener una velocidad de asm x86 eficiente, que incluye una versión AVX512VBMI que solo contiene 2 instrucciones (pero necesita vectores de control para vpmultishiftqb y vpshufb así que no sería genial para el golf). Normalmente se necesita trabajo adicional para que SIMD invierta byte en orden de impresión en little endian x86, por lo que esta salida hexadecimal invertida en byte es realmente más fácil de lo normal.


Otras ideas

Pensé en tomar el entero por referencia y recorrer sus bytes con char*, en una implementación C poco endian (como x86 o ARM). Pero no creo que eso hubiera ahorrado mucho.

Utilizando sprintfpara hacer 1 byte a la vez, 64 bytes después de jugar al golf:

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

Pero si usamos funciones similares a printf, también podríamos intercambiar byte y hacer una %ximpresión de todo, como la respuesta de @ JL2210 .

Peter Cordes
fuente
⭐ - obtienes una estrella por una buena respuesta
Kamil Kiełczewski
3

Código de máquina SIMD x86 (AVX512-VBMI), 36 bytes

(16 bytes de los cuales son una tabla de búsqueda hexadecimal)

Esta es una función que toma un número entero xmm0y devuelve 8 bytes de datos de caracteres ASCII xmm0, para que la persona que llama los almacene donde quiera. (por ejemplo, a la memoria de video después de intercalar con bytes de atributo, o en una cadena en construcción, o lo que sea)

Desde C, llámelo como __m128i retval = lehex(_mm_cvtsi32_si128(x))con la convención de llamadas x86-64 System V, o 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"

Total = 0x24 = 36 bytes.

Ver ¿Cómo convertir un número a hexadecimal? en SO por cómo funciona esto. (SSE2 para shift / punpck, luego vpermbguarda el trabajo que necesitaríamos pshufb. AVX1 en lugar de SSE2 / SSSE3 también evita una movapscopia de registro).

Observe que punpcklbwcon los operandos de origen en ese orden nos dará el mordisco más significativo del byte de entrada bajo en el elemento de byte más bajo, luego el mordisco menos significativo del byte de origen más bajo. (En esa respuesta SO, bswapse usa a en la entrada para obtener un resultado en el orden de impresión estándar con solo SSE2. Pero aquí queremos ese orden: mordisco alto en el elemento inferior dentro de cada byte, pero aún así el orden de los bytes little endian).

Si tuviéramos más constantes de datos, podríamos ahorrar espacio en el modo de direccionamiento haciendo uno y mov edx, imm32luego utilizando [rdx+16]o cualquier modo de direccionamiento. O vpbroadcastb xmm0, [rdx+1].

Pero creo que un LUT + hexadecimal de 16 bytes vpermbes aún mejor que implementar la n>9 : n+'a'-10 : n+'0'condición: eso requiere 3 constantes y al menos 3 instrucciones con el enmascaramiento de bytes AVX512BW (compárelo en máscara vpaddb, fusión-enmascarado vpaddb), o más con AVX1 o SSE2. (Consulte ¿Cómo convertir un número a hexadecimal? En SO para obtener una versión SSE2 de eso). Y cada instrucción AVX512BW tiene al menos 6 bytes de longitud (EVEX de 4 bytes + código de operación + modrm), más larga con un desplazamiento en el modo de direccionamiento.

En realidad, se necesitarían al menos 4 instrucciones porque necesitamos limpiar la basura alta con andps(o EVEX vpanddcon un operando de memoria de transmisión de 4 bytes) antes de la comparación. Y cada uno de ellos necesita una constante de vector diferente. AVX512 ha emitido operandos de memoria, pero solo para elementos de 32 bits y más. por ejemplo , el último operando de EVEXvpaddb es solo xmm3/m128, no xmm3/m128/m8bcst. (Los puertos de carga de Intel solo pueden hacer transmisiones de 32 y 64 bits de forma gratuita como parte de una carga ascendente, por lo que Intel diseñó AVX512BW para reflejar eso y no poder codificar los operandos de memoria de transmisión de bytes o palabras, en lugar de darles la opción de haz transmisiones de dword para que puedas comprimir tus constantes a 4 bytes: /.)

La razón por la que usé AVX512VBMI envpermb lugar de SSSE3 / AVX1 pshufbes doble:

  • vpermbignora los bits altos de los selectores. (v)pshufbceros bytes de acuerdo con el bit alto del vector de control y hubieran necesitado un extra pando andpsrealmente aislar nibbles. Con el tamaño XMM / 16 bytes, vpermbsolo mira los 4 bits bajos de los elementos de control aleatorio, es decir, bits [3:0]en la notación de Intel en la sección Operación .
  • vpermbpuede tomar los datos que se barajan (la tabla de búsqueda) como un operando de memoria. (v)pshufbEl operando xmm / mem es el vector de control aleatorio.

Tenga en cuenta que AVX512VBMI solo está disponible en CannonLake / Ice Lake, por lo que probablemente necesite un simulador para probar esto, como el SDE de Intel.

Peter Cordes
fuente
⭐ - obtienes una estrella por una buena respuesta
Kamil Kiełczewski
@ KamilKiełczewski: lol gracias. Convertir números a hexadecimal eficientemente es una de mis cosas favoritas. Es un buen caso de uso para varios trucos geniales y manipulación de bits.
Peter Cordes
3

Scala , 58 40 36 bytes

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

Pruébalo en línea!

Todavía usa el incorporado para revertir los bytes de un Int, pero usa formatpara formatearlo Intcomo un Hex. No hay necesidad de llamar toHexString.

Eliminado los parens en format. Esto ahora significa que el argumento se puede tomar implícitamente usando _.

Jabonoso
fuente
2

Adelante (gforth) , 52 51 40 bytes

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

Pruébalo en línea!

Explicación del código

: 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
fuente
2

Jalea , 13 bytes

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

Pruébalo en línea!

Un programa completo que toma un entero como argumento e imprime una cadena.

Nick Kennedy
fuente
🚀 tu respuesta está en la parte superior
Kamil Kiełczewski
2

Excel, 91 bytes

=RIGHT(DEC2HEX(A1,8),2)&MID(DEC2HEX(A1,8),5,2)&MID(DEC2HEX(A1,8),3,2)&LEFT(DEC2HEX(A1,8),2)
Wernisch
fuente
2

K4 , 12 11 bytes

Solución:

,/$|4_0x0\:

Ejemplos:

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

Explicación:

Más o menos exactamente lo que pregunta la pregunta:

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

Notas:

  • -1 byte ya que los números K4 son largos (64 bits) de forma predeterminada, por lo que se eliminan 4 bytes (32 bits)
callejero
fuente
🚀 su respuesta está en la parte superior
Kamil Kiełczewski
2

PHP , 31 bytes

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

Pruébalo en línea!

Aprovechando el paquete y desempaquetado de PHP , empaco la entrada sin firmar con el formato "32 bit little endian byte order" ( V) en una cadena binaria y luego la descomprimo con el formato "cadena hexadecimal, primer nibble alto" ( H) e imprimo el resultado.

¡Este parece ser uno de los raros casos en los que las funciones integradas de PHP son más cortas que la implementación de un algoritmo simple!

Noche2
fuente
Las funciones pack()/ de PHP unpack()son increíbles para las 0 veces que las necesita en la mayoría de los proyectos PHP. ¡Felicidades, has encontrado su uso!
640 KB el
1

Carbón , 11 bytes

⪫⮌⪪﹪%08xN²ω

Pruébalo en línea! El enlace es a la versión detallada del código. Explicación:

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

19 bytes sin recurrir al formato Python:

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

Pruébalo en línea! El enlace es a la versión detallada del código. Explicación:

           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
fuente
🚀 tu respuesta está en la parte superior
Kamil Kiełczewski
1

J , 10 bytes

8{._1{3!:3

Pruébalo en línea!

cómo

3!:3es una J "conjunción extranjera" para representación hexadecimal, documentada aquí . Es decir, es un generador incorporado para convertir a hexadecimal. Sin embargo, su salida no es exactamente lo que queremos. Por ejemplo, corriendo:

3!:3 (304767)

produce:

e300000000000000
0400000000000000
0100000000000000
0000000000000000
7fa6040000000000

El significado de las otras líneas se explica en la página de documentos a la que he vinculado anteriormente. En cualquier caso, está claro que queremos los primeros 8 caracteres de la última línea.

_1{ Consigue la última línea.

8{. obtiene los primeros 8 caracteres de la misma.

Jonás
fuente
🚀 tu respuesta está en la parte superior
Kamil Kiełczewski
1

Ruby , 31 27 bytes

Terminó siendo un puerto de la respuesta PHP de Night2 porque Ruby tiene la misma funcionalidad de empaque / desempaque.

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

Pruébalo en línea!

Mi respuesta original de 31 bytes que no aprovechó el modo de desempaquetado H8 porque no lo sabía:

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

Pruébalo en línea!

Tinta de valor
fuente
1

Lote de Windows, 90 bytes

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

Ejecute la línea de comandos con / v para habilitar la expansión retrasada.

Peter Ferrie
fuente
1

Código de máquina x86 de 32 bits, 24 21 bytes

registro de cambios: -3 bytes: reemplace add / cmp / jbe / add estándar con un hack de DAS por @peter ferrie

64 bits: todavía 24 bytes. El modo largo eliminó el código de operación DAS.
Modo de 16 bits: el tamaño de operando predeterminado es de 16 bits, pero la especificación del problema es inherentemente de 32 bits. Incluidos 8 dígitos hexadecimales codificados.


Byte-reverse con bswapluego manual int-> hex en orden estándar (el mordisco más significativo primero, escribiendo dígitos hexadecimales en un buffer de salida de caracteres en orden ascendente). Esto evita la necesidad de desenrollar el bucle para cambiar el orden entre mordiscos dentro de un byte vs. a través de bytes.

Se puede llamar como void lehex(char buf[8] /*edi*/, uint32_t x /*esi*/);x86-64 System V, excepto que esto no funciona en modo de 64 bits. (Necesita el puntero de salida en EDI para stosb. El número de entrada puede estar en cualquier registro que no sea ECX o 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

tamaño = 0x15 = 21 bytes.

TIO FASM Caso de prueba x86 de 32 bits con una llamada asm que utiliza una writellamada al sistema para escribir la salida después de llamarla dos veces para agregar 2 cadenas a un búfer. Prueba todos los dígitos hexadecimales 0..F, incluidos 9 y A en el límite entre el número y la letra.

El DAShack - x86 tiene una bandera de medio acarreo, para llevar a cabo el mordisco bajo. Útil para cosas BCD empaquetadas como la instrucción DAS, pensadas para usar después de restar dos enteros BCD de 2 dígitos. Con el mordisco bajo de AL estando fuera del rango 0-9, definitivamente estamos abusando de esto aquí.

Observe if (old_AL > 99H) or (old_CF = 1)ENTONCES la AL ← AL − 60H;parte de la sección Operación en el manual; sbb siempre establece CF aquí para que esa parte siempre suceda. Eso y el rango ASCII para letras mayúsculas es lo que motiva la elección desub al, 0x69

  • cmp 0xD, 0xA no establece CF
  • sbb se 0xD - 0x69ajusta a AL = 0xA4como entrada a DAS. (Y establece CF, borra AF)
  • no AL - = 6 en la primera parte de DAS (porque 4> 9 es falso y AF = 0)
  • AL - = 0x60 en la segunda parte, dejando 0x44, el código ASCII para'D'

contra un número:

  • cmp 0x3, 0xA establece CF
  • sbb 3 - 0x69 - 1= AL = 0x99 y establece CF y AF
  • no AL - = 6 en la primera parte de DAS (9> 9 es falso pero AF está configurado), dejando 0x93
  • AL - = 0x60 en la segunda parte, dejando 0x33, el código ASCII para '3'.

Restar 0x6aen SBB establecerá AF para cada dígito <= 9, por lo que todos los números siguen la misma lógica. Y déjelo despejado para cada dígito hexadecimal alfabético. es decir, explotar correctamente el manejo dividido 9 / A de DAS.


Normalmente (para el rendimiento) usaría una tabla de búsqueda para un bucle escalar, o posiblemente un 2x sin ramificaciones leay una cmp/cmovadición condicional. Pero las al, imm8instrucciones de 2 bytes son una gran victoria para el tamaño del código.


Versión de la versión x86-64 : solo la parte que es diferente, entre and al, 0xfy 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:

Tenga en cuenta que add al, '0' siempre se ejecuta, y el complemento condicional solo agrega la diferencia entre 'a'-10y '0', para que sea solo un en iflugar de if/ else.

Probado y funciona, usando la misma mainllamada que mi respuesta C , que usa char buf[8]y printf("%.8s\n", buf).

Peter Cordes
fuente
¿Puedes crear fragmentos de trabajo en línea, por ejemplo, aquí ?
Kamil Kiełczewski
@ KamilKiełczewski: TIO hace que sea imposible (AFAIK) escribir la persona que llama en C para probar una función asm, por lo que a menudo no me molesto, pero seguro, ya que usted preguntó y sys_writepuede generar cadenas de longitud fija fácilmente. Es interesante, no me había dado cuenta de que FASM en TIO te permitía crear ejecutables de 32 bits, a diferencia de NASM, donde no respeta -felf32. Prefiero x86-64 de todos modos, y esta respuesta no guarda ningún byte del código de 32 bits.
Peter Cordes
⭐ - obtienes una estrella por una buena respuesta
Kamil Kiełczewski
1
@ JL2210: ¿Quieres decir sprintf? No creo que libc tenga ninguna función práctica int-> string que no sean las basadas en formato-string, solo string-> int como strtoul. Pero sí, bswap / printf probablemente sería más corto, si puede encontrar alguna forma de contar bytes para la entrada GOT para una función en una biblioteca dinámica (además del call [rel printf wrt ..got]sitio de llamada de 6 bytes ); Un mínimo de ejecutables enlazados estáticamente puede ser significativamente más pequeño que dinámico, al menos cuando se realiza ldcon valores predeterminados normales. Pero no creo que sea razonable vincularlo estáticamente, pero no contar el tamaño de su código.
Peter Cordes
1
@ JL2210: Recuerde, esta es una respuesta de código de máquina x86 , no un tamaño de fuente de texto asm. No he usado las funciones de libc en respuestas anteriores de código de máquina, solo llamadas al sistema Linux (por ejemplo, en Fibonacci), e IDK cómo haría para contar el costo o si incluso quiero escribir respuestas de código de máquina con libc . Hay casos de uso para el código de máquina x86 donde una libc no está disponible, por ejemplo, en un gestor de arranque.
Peter Cordes