¿Cómo combinar dos enteros de 32 bits en un entero de 64 bits?

2 minutos de lectura

avatar de usuario
bei

Tengo un registro de conteo, que se compone de dos enteros sin signo de 32 bits, uno para los 32 bits más altos del valor (palabra más significativa) y otro para los 32 bits más bajos del valor (palabra menos significativa).

¿Cuál es la mejor manera en C de combinar estos dos enteros sin signo de 32 bits y luego mostrarlos como un número grande?

En especifico:

leastSignificantWord = 4294967295; //2^32-1

printf("Counter: %u%u", mostSignificantWord,leastSignificantWord);

Esto se imprimiría bien.

Cuando el número se incrementa a 4294967296, tengo que la palabra menos significativa pasa a 0, y la palabra más significativa (0 inicialmente) ahora es 1. El contador completo ahora debe leer 4294967296, pero en este momento solo lee 10, porque solo estoy concatenando 1 de la palabra más significativa y 0 de la palabra menos significativa.

¿Cómo debo hacer que muestre 4294967296 en lugar de 10?

  • Su método solo tendría sentido si usara %8.8X como especificador de formato para concatenar los valores hexadecimales de las palabras. Para que funcione en decimal, el valor entero máximo tendría que ser (10^n)-1 donde n era el número de dígitos decimales, ¡y ese nunca será el caso en una máquina binaria!

    – Clifford

    4 mayo 2010 a las 21:34


avatar de usuario
jfs

Podría ser ventajoso utilizar no firmado enteros con explícito tamaños en este caso:

#include <stdio.h>
#include <inttypes.h>

int main(void) {
  uint32_t leastSignificantWord = 0;
  uint32_t mostSignificantWord = 1;
  uint64_t i = (uint64_t) mostSignificantWord << 32 | leastSignificantWord;
  printf("%" PRIu64 "\n", i);

  return 0;
}

Producción

4294967296

Desglose de (uint64_t) mostSignificantWord << 32 | leastSignificantWord

  • (typename) lo hace encasillamiento en C. Cambia el tipo de datos de valor a typename.

    (uint64_t) 0x00000001 -> 0x0000000000000001

  • << lo hace Shift izquierdo. En C, el desplazamiento a la izquierda en enteros sin signo realiza cambio lógico.

    0x0000000000000001 << 32 -> 0x0000000100000000

desplazamiento lógico a la izquierda

  • | lo hace ‘bit a bit o’ (OR lógico en bits de los operandos).

    0b0101 | 0b1001 -> 0b1101

  • Brillante. Alguien que no solo sabe cómo usar los tipos C99 con tamaños explícitos, sino que también entiende cómo usar los tipos relacionados printf macros +10 si pudiera. SO-ers, uníos! ¡Esta respuesta merece la mejor facturación!

    —Norman Ramsey

    5 de mayo de 2010 a las 0:11

  • ¿Alguien podría desglosar lo que significa esta línea? uint64_t i = (uint64_t) palabra más significativa << 32 | palabra menos significativa; ¿Y qué es el PRIu64? A mi compilador no parece gustarle, dice "esperado ) después de PRIu64.

    – bei

    5 mayo 2010 a las 15:59

  • @Bei337: inttypes.h es una parte del estándar C99. Define especificadores de formato para printf/scanf tal como PRIu64 e incluye stdint.h que define typedefs tales como uint32_t. Si está utilizando MSVC ++ / Windows, tome inttypes.h desde code.google.com/p/msinttypes

    – jfs

    5 mayo 2010 a las 16:33

  • @ Bei337: Podría ayudar si compila el código como código C (no como C++). C++ requiere __STDC_FORMAT_MACROS por definir para usar PRI.. macros

    – jfs

    5 mayo 2010 a las 20:52

  • @PremalShah: uint32_t lo = (uint32_t)n, hi = (n >> 32);donde n es un unit64_t variable.

    – jfs

    30 de agosto de 2012 a las 14:16

avatar de usuario
twk

long long val = (long long) mostSignificantWord << 32 | leastSignificantWord;
printf( "%lli", val );

  • << 32 no funcionará bien si mostSignificantWord es un entero de 32 bits. Primero debe convertirse en un tipo de 64 bits. Por cierto, en g++ long y long long Ambos son de 8 bytes.

    – Alex Korbán

    4 mayo 2010 a las 21:25

  • @Alex: en g++ en x64, long es de 64 bits En x86 es de 32 bits.

    –Steve Jessop

    4 mayo 2010 a las 22:27

  • Utilice tipos C99 uint32_t y uint64_t. Por eso están ahí.

    —Norman Ramsey

    5 de mayo de 2010 a las 0:09

  • las entradas son unsignedentonces, en ausencia de que OP diga lo contrario, ¿no debería ser también la salida? long long por sí solo implica signed long long int.

    – subrayado_d

    1 de enero de 2019 a las 22:38


mi toma:

unsigned int low = <SOME-32-BIT-CONSTRANT>
unsigned int high = <SOME-32-BIT-CONSTANT>

unsigned long long data64;

data64 = (unsigned long long) high << 32 | low;

printf ("%llx\n", data64); /* hexadecimal output */
printf ("%lld\n", data64); /* decimal output */

Otro enfoque:

unsigned int low = <SOME-32-BIT-CONSTRANT>
unsigned int high = <SOME-32-BIT-CONSTANT>

unsigned long long data64;
unsigned char * ptr = (unsigned char *) &data;

memcpy (ptr+0, &low, 4);
memcpy (ptr+4, &high, 4);

printf ("%llx\n", data64); /* hexadecimal output */
printf ("%lld\n", data64); /* decimal output */

Ambas versiones funcionan y tendrán un rendimiento similar (el compilador optimizará el memcpy).

La segunda versión no funciona con objetivos big-endian, pero elimina las conjeturas si la constante 32 debe ser 32 o 32ull. Algo de lo que nunca estoy seguro cuando veo turnos con constantes mayores a 31.

  • Es irrelevante si la constante de cantidad de cambio es 32 o 32ULL, ya que ambos tienen el mismo valor y eso es todo lo que se usa para el turno. Siempre que el valor que se desplaza tenga el tipo unsigned long longtodo es miel sobre hojuelas.

    – café

    4 mayo 2010 a las 22:00

  • ¿Por qué incluso mostrar la solución no portátil? Ahora alguien lo copiará, y luego el código se moverá a otra aplicación, y algo eventualmente se romperá y llevará una eternidad depurarlo.

    – Adrián McCarthy

    4 mayo 2010 a las 22:23

  • Utilice los tipos C99 con tamaños explícitos. Es por eso que están allí.

    —Norman Ramsey

    5 de mayo de 2010 a las 0:10

avatar de usuario
caiohamamura

Hay otra forma de usar matrices y punteros:

#include <stdio.h>
#include <inttypes.h>

int main(void) {
    // Two uint32_t to one uint64_t
    uint32_t val1[2] = {1000, 90000};
    uint64_t *val1_u64_ptr = (uint64_t*)val1; //intermediate pointer cast to avoid Wstrict-aliasing warnings
    uint64_t val2 = *val1_u64_ptr;
    printf("val2: %" PRIu64 "\n", val2);
    // val2: 386547056641000


    // back to uint32_t array from uint64_t
    uint64_t val3 = 386547056641000ull;
    uint32_t *val4 = (uint32_t*)&val3;
    printf("val4: %" PRIu32 ", %" PRIu32 "\n", val4[0], val4[1]);
    // val4: 1000, 90000
    
    return 0;
}

Este código para mí es mucho más fácil de entender y leer. Simplemente está creando un espacio contiguo en la memoria con dos 32-bit unsigned int y luego este mismo espacio de memoria se lee como un solo 64-bit unsigned int valor y viceversa. No hay operaciones involucradas, solo la memoria se lee como diferentes tipos.

EDITAR

Olvidé mencionar que esto es genial si ya tienes un 64-bit array leer desde algún lugar, entonces podría leer fácilmente todo como 32-bit array pares:

#include <stdio.h>
#include <inttypes.h>

int main() {
    uint64_t array64[] = {
        386547056641000ull,
        93929935171414ull,
        186655006591110ull,
        73141496240875ull,
        161460097995400ull,
        351282298325439ull,
        97310615654411ull,
        104561732955680ull,
        383587691986172ull,
        386547056641000ull
    };

    int n_items = sizeof(array64) / sizeof(array64[0]);
    
    uint32_t* array32 = (uint32_t*)&array64;
    for (int ii = 0; ii < n_items * 2; ii += 2) {
        printf("[%" PRIu32 ", %" PRIu32 "]\n", array32[ii], array32[ii + 1]);
    }

    return 0;
}

Producción:

[1000, 90000]
[3295375190, 21869]
[22874246, 43459]
[2498157291, 17029]
[3687404168, 37592]
[1218152895, 81789]
[3836596235, 22656]
[754134560, 24345]
[4162780412, 89310]
[1000, 90000]

Usando la estructura de unión

Aún mejor y más legible sería usar una unión de estructura a partir de https://stackoverflow.com/a/2810339/2548351:

#include <stdio.h>
#include <inttypes.h>

typedef union {
  int64_t big;
  struct {
    int32_t x;
    int32_t y;
  };
} xy_t;

int main() {
    // initialize from 64-bit
    xy_t value = {386547056641000ull};
    printf("[%" PRIu32 ",%" PRIu32 "]\n", value.x, value.y);
    // [1000, 90000]
    
    // initialize as two 32-bit
    xy_t value2 = {.x = 1000, .y = 90000};
    printf("%" PRIu64, value.big);
    // 386547056641000

    return 0;
}

En lugar de intentar imprimir decimales, a menudo imprimo en hexadecimal.

Por lo tanto …

printf ("0x%x%08x\n", upper32, lower32);

Alternativamente, dependiendo de la arquitectura, la plataforma y el compilador, a veces puede salirse con la suya con algo como…

printf ("%lld\n", lower32, upper32);

o

printf ("%lld\n", upper32, lower32);

Sin embargo, este método alternativo depende mucho de la máquina (endian-ness, así como 64 frente a 32 bits, …) y, en general, no se recomienda.

Espero que esto ayude.

avatar de usuario
Juan Mellado

Este código funciona cuando tanto upper32 como lower32 son negativos:

data64 = ((LONGLONG)upper32<< 32) | ((LONGLONG)lower32& 0xffffffff);

avatar de usuario
Wladimir Palant

Puede hacerlo escribiendo los valores de 32 bits en las ubicaciones correctas de la memoria:

unsigned long int data64;
data64=lowerword
*(&((unsigned int)data64)+1)=upperword;

Sin embargo, esto depende de la máquina, por ejemplo, no funcionará correctamente en procesadores big-endian.

  • podrías elaborar y explicar tu respuesta

    – paloma

    19 de octubre de 2012 a las 11:04

  • esto solo funcionará en máquinas little-endian. Una suposición más es que el largo sin firmar es de 64 bits y el int sin firmar es de 32 bits (puede usar uint32 y uint64). ahora la explicación data64=lowerword esto copiará lowerword a los bytes inferiores de data64. (int sin firmar) data64 convertirlo a 32 bits. &((unsigned int)data64) me da la dirección de tipo unsigned int agregando 1 incrementará la dirección a los bytes más altos de data64 finalmente * de esto almacena la palabra superior en esos bytes más altos.

    – mohit

    20 oct 2012 a las 9:30


  • Esto tiene un comportamiento indefinido; viola el aliasing estricto. Utilizar memcpy si quieres hacer esto, o mejor no lo hagas. Los compiladores entienden ((uint64_t)a<<32) | b

    – Peter Cordes

    5 de junio de 2019 a las 2:00

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad