Eu quero definir uma função que recebe um unsigned int
como argumento e retorna um int
módulo congruente UINT_MAX + 1 para o argumento.
Uma primeira tentativa pode ser assim:
int unsigned_to_signed(unsigned n)
{
return static_cast<int>(n);
}
Mas, como qualquer advogado de linguagem sabe, a conversão de não assinado para assinado para valores maiores que INT_MAX é definida pela implementação.
Desejo implementar isso de forma que (a) dependa apenas do comportamento exigido pela especificação; e (b) compila em um ambiente autônomo em qualquer máquina moderna e otimizando o compilador.
Quanto a máquinas bizarras ... Se não houver nenhum módulo congruente int assinado UINT_MAX + 1 para o int não assinado, digamos que eu queira lançar uma exceção. Se houver mais de um (não tenho certeza se isso é possível), digamos que eu queira o maior.
OK, segunda tentativa:
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
}
Não me importo muito com a eficiência quando não estou em um sistema típico de complemento de dois, pois, em minha humilde opinião, isso é improvável. E se meu código se tornar um gargalo nos sistemas onipresentes de magnitude de sinal de 2050, bem, aposto que alguém pode descobrir isso e otimizá-lo então.
Agora, essa segunda tentativa está bem perto do que eu quero. Embora a conversão para int
seja definida pela implementação para algumas entradas, a unsigned
conversão de volta para é garantida pelo padrão para preservar o valor módulo UINT_MAX + 1. Portanto, a condicional verifica exatamente o que eu quero e não compila em nenhum sistema que eu possa encontrar.
No entanto ... Ainda estou lançando para int
sem primeiro verificar se ele invocará o comportamento definido pela implementação. Em algum sistema hipotético em 2050, poderia fazer sabe-se lá o quê. Então, digamos que eu queira evitar isso.
Pergunta: Qual deve ser a aparência da minha "terceira tentativa"?
Para recapitular, eu quero:
- Cast de int não assinado para int assinado
- Preserve o valor mod UINT_MAX + 1
- Invoque apenas o comportamento obrigatório padrão
- Compilar em um ambiente autônomo em uma máquina típica de complemento de dois com compilador de otimização
[Atualizar]
Deixe-me dar um exemplo para mostrar por que essa não é uma questão trivial.
Considere uma implementação hipotética de C ++ com as seguintes propriedades:
sizeof(int)
é igual a 4sizeof(unsigned)
é igual a 4INT_MAX
é igual a 32767INT_MIN
é igual a -2 32 + 32768UINT_MAX
é igual a 2 32 - 1- Em aritmética
int
é módulo 2 de 32 (para o intervaloINT_MIN
através deINT_MAX
) std::numeric_limits<int>::is_modulo
é verdade- Casting unsigned
n
to int preserva o valor para 0 <= n <= 32767 e retorna zero caso contrário
Nesta implementação hipotética, há exatamente um int
valor congruente (mod UINT_MAX + 1) para cada unsigned
valor. Então minha pergunta ficaria bem definida.
Eu afirmo que essa implementação hipotética de C ++ está em total conformidade com as especificações C ++ 98, C ++ 03 e C ++ 11. Admito que não memorizei cada palavra de todos eles ... Mas acredito que li as seções relevantes com atenção. Portanto, se quiser que eu aceite sua resposta, você deve (a) citar uma especificação que exclui essa implementação hipotética ou (b) tratá-la corretamente.
Na verdade, uma resposta correta deve lidar com cada implementação hipotética permitida pelo padrão. Isso é o que significa, por definição, "invocar apenas o comportamento determinado por padrão".
A propósito, observe que std::numeric_limits<int>::is_modulo
é totalmente inútil aqui por vários motivos. Por um lado, pode ser true
mesmo que as conversões não assinadas para assinadas não funcionem para grandes valores não assinados. Por outro lado, pode ser true
até mesmo nos sistemas de complemento de alguém ou magnitude de sinal, se a aritmética for simplesmente um módulo de todo o intervalo inteiro. E assim por diante. Se sua resposta depender de is_modulo
, está errado.
[Atualização 2]
A resposta de hvd me ensinou algo: minha implementação hipotética de C ++ para inteiros não é permitida pelo C. moderno. Os padrões C99 e C11 são muito específicos sobre a representação de inteiros assinados; na verdade, eles apenas permitem complemento de dois, complemento de uns e magnitude de sinal (seção 6.2.6.2 parágrafo (2);).
Mas C ++ não é C. Como descobri, esse fato está no cerne da minha pergunta.
O padrão C ++ 98 original foi baseado no C89 muito mais antigo, que diz (seção 3.1.2.5):
Para cada um dos tipos inteiros com sinal, há um tipo inteiro sem sinal correspondente (mas diferente) (designado com a palavra-chave unsigned) que usa a mesma quantidade de armazenamento (incluindo informações de sinal) e tem os mesmos requisitos de alinhamento. O intervalo de valores não negativos de um tipo inteiro com sinal é um subintervalo do tipo inteiro sem sinal correspondente, e a representação do mesmo valor em cada tipo é a mesma.
C89 não diz nada sobre ter apenas um bit de sinal ou apenas permitir complemento de dois / complemento de uns / magnitude de sinal.
O padrão C ++ 98 adotou esta linguagem quase literalmente (seção 3.9.1 parágrafo (3)):
Para cada um dos tipos de número inteiro com sinal, existe um tipo de número inteiro sem sinal correspondente (mas diferente) : "
unsigned char
", "unsigned short int
", "unsigned int
" e "unsigned long int
", cada um dos quais ocupa a mesma quantidade de armazenamento e tem os mesmos requisitos de alinhamento (3,9 ) como o tipo inteiro com sinal correspondente; ou seja, cada tipo de inteiro não assinado tem a mesma representação de objeto que seu tipo inteiro não assinado correspondente . A faixa de valores não negativos de um tipo inteiro com sinal é uma subfaixa do tipo inteiro sem sinal correspondente, e a representação do valor de cada tipo com sinal / sem sinal correspondente deve ser a mesma.
O padrão C ++ 03 usa linguagem essencialmente idêntica, assim como o C ++ 11.
Nenhuma especificação C ++ padrão restringe suas representações de inteiros assinados a qualquer especificação C, pelo que posso dizer. E não há nada que obrigue um bit de sinal único ou algo do tipo. Tudo o que diz é que inteiros não negativos com sinal devem ser um subintervalo do não sinal correspondente.
Então, novamente eu afirmo que INT_MAX = 32767 com INT_MIN = -2 32 +32768 é permitido. Se sua resposta presumir o contrário, está incorreta, a menos que você cite um padrão C ++ que prove que estou errado.