Quiero definir una función que tome un unsigned int
argumento como y devuelva un int
módulo congruente UINT_MAX + 1 al argumento.
Un primer intento podría verse así:
int unsigned_to_signed(unsigned n)
{
return static_cast<int>(n);
}
Pero como sabe cualquier abogado de idiomas, la conversión de valores sin firmar a firmados para valores mayores que INT_MAX está definida por la implementación.
Quiero implementar esto de tal manera que (a) solo se base en el comportamiento exigido por la especificación; y (b) compila en un no-op en cualquier máquina moderna y optimiza el compilador.
En cuanto a las máquinas extrañas ... Si no hay un int congruente con el módulo UINT_MAX + 1 con el int sin firmar, digamos que quiero lanzar una excepción. Si hay más de uno (no estoy seguro de que sea posible), digamos que quiero el más grande.
OK, segundo intento:
int unsigned_to_signed(unsigned n)
{
int int_n = static_cast<int>(n);
if (n == static_cast<unsigned>(int_n))
return int_n;
// else do something long and complicated
}
No me importa mucho la eficiencia cuando no estoy en un sistema típico de complemento a dos, ya que en mi humilde opinión eso es poco probable. Y si mi código se convierte en un cuello de botella en los omnipresentes sistemas de magnitud de signo de 2050, bueno, apuesto a que alguien puede resolverlo y optimizarlo entonces.
Ahora, este segundo intento está bastante cerca de lo que quiero. Aunque la conversión a int
está definida por la implementación para algunas entradas, la unsigned
conversión a está garantizada por el estándar para preservar el valor módulo UINT_MAX + 1. Entonces, el condicional verifica exactamente lo que quiero, y no se compilará en ningún sistema que pueda encontrar.
Sin embargo ... todavía estoy lanzando int
sin verificar primero si invocará el comportamiento definido por la implementación. En algún sistema hipotético en 2050 podría hacer quién sabe qué. Digamos que quiero evitar eso.
Pregunta: ¿Cómo debería ser mi "tercer intento"?
En resumen, quiero:
- Transmitir de int sin firmar a int firmado
- Conservar el valor mod UINT_MAX + 1
- Invocar solo el comportamiento obligatorio estándar
- Compile en una operación no operativa en una máquina típica de complemento a dos con compilador de optimización
[Actualizar]
Permítanme dar un ejemplo para mostrar por qué esta no es una pregunta trivial.
Considere una implementación hipotética de C ++ con las siguientes propiedades:
sizeof(int)
es igual a 4sizeof(unsigned)
es igual a 4INT_MAX
es igual a 32767INT_MIN
es igual a -2 32 + 32768UINT_MAX
es igual a 2 32 - 1- La aritmética
int
activada es módulo 2 32 (en el rangoINT_MIN
hastaINT_MAX
) std::numeric_limits<int>::is_modulo
es verdad- La conversión unsigned
n
to int conserva el valor de 0 <= n <= 32767 y, en caso contrario, arroja cero
En esta implementación hipotética, hay exactamente un int
valor congruente (mod UINT_MAX + 1) para cada unsigned
valor. Entonces mi pregunta estaría bien definida.
Afirmo que esta implementación hipotética de C ++ cumple totalmente con las especificaciones de C ++ 98, C ++ 03 y C ++ 11. Admito que no he memorizado cada palabra de todas ... Pero creo que he leído las secciones relevantes con atención. Entonces, si desea que acepte su respuesta, debe (a) citar una especificación que descarte esta implementación hipotética o (b) manejarla correctamente.
De hecho, una respuesta correcta debe manejar cada implementación hipotética permitida por el estándar. Eso es lo que significa, por definición, "invocar sólo el comportamiento obligatorio estándar".
Por cierto, tenga en cuenta que std::numeric_limits<int>::is_modulo
aquí es completamente inútil por múltiples razones. Por un lado, puede ser true
incluso si las conversiones sin firmar no funcionan para valores sin firmar grandes. Por otro lado, puede ser true
incluso en sistemas de complemento a uno o de magnitud de signo, si la aritmética es simplemente módulo todo el rango entero. Y así. Si su respuesta depende de is_modulo
, está mal.
[Actualización 2]
La respuesta de hvd me enseñó algo: Mi implementación hipotética de C ++ para enteros no está permitida por el C. moderno. Los estándares C99 y C11 son muy específicos sobre la representación de enteros con signo; de hecho, solo permiten complemento a dos, complemento a uno y magnitud de signo (sección 6.2.6.2 párrafo (2);).
Pero C ++ no es C. Como resultado, este hecho está en el corazón de mi pregunta.
El estándar C ++ 98 original se basó en el C89 mucho más antiguo, que dice (sección 3.1.2.5):
Para cada uno de los tipos de enteros con signo, existe un tipo de entero sin signo correspondiente (pero diferente) (designado con la palabra clave unsigned) que usa la misma cantidad de almacenamiento (incluida la información de signo) y tiene los mismos requisitos de alineación. El rango de valores no negativos de un tipo de entero con signo es un subrango del tipo de entero sin signo correspondiente, y la representación del mismo valor en cada tipo es la misma.
C89 no dice nada sobre tener solo un bit de signo o solo permitir dos-complemento / uno-complemento / signo-magnitud.
El estándar C ++ 98 adoptó este lenguaje casi literalmente (sección 3.9.1 párrafo (3)):
Para cada uno de los tipos de enteros con signo, existe un tipo de entero sin signo correspondiente (pero diferente) : "
unsigned char
", "unsigned short int
", "unsigned int
" y "unsigned long int
", cada uno de los cuales ocupa la misma cantidad de almacenamiento y tiene los mismos requisitos de alineación (3.9 ) como el tipo entero con signo correspondiente; es decir, cada tipo de entero con signo tiene la misma representación de objeto que su correspondiente tipo de entero sin signo . El rango de valores no negativos de un tipo de entero con signo es un subrango del tipo de entero sin signo correspondiente, y la representación del valor de cada tipo con / sin signo correspondiente será la misma.
El estándar C ++ 03 utiliza un lenguaje esencialmente idéntico, al igual que C ++ 11.
Ninguna especificación estándar de C ++ restringe sus representaciones enteras con signo a ninguna especificación de C, por lo que puedo decir. Y no hay nada que exija un bit de signo único ni nada por el estilo. Todo lo que dice es que los enteros con signo no negativo deben ser un subrango del correspondiente sin signo.
Entonces, nuevamente afirmo que INT_MAX = 32767 con INT_MIN = -2 32 +32768 está permitido. Si su respuesta asume lo contrario, es incorrecta a menos que cite un estándar C ++ que demuestre que estoy equivocado.