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.
int
precisa de pelo menos 33 bits para representá-la. Eu sei que é apenas uma nota de rodapé, então você pode argumentar que não é normativa, mas acho que a nota de rodapé 49 em C ++ 11 se destina a ser verdadeira (já que é uma definição de um termo usado no padrão) e não contradiz qualquer coisa explicitamente declarada no texto normativo. Portanto, todos os valores negativos devem ser representados por um padrão de bits no qual o bit mais alto é definido e, portanto, você não pode agrupá-2^32 - 32768
los em 32 bits. Não que seu argumento dependa de alguma forma do tamanho deint
.Respostas:
Expandindo a resposta do usuário71404:
int f(unsigned x) { if (x <= INT_MAX) return static_cast<int>(x); if (x >= INT_MIN) return static_cast<int>(x - INT_MIN) + INT_MIN; throw x; // Or whatever else you like }
Se
x >= INT_MIN
(mantenha as regras de promoção em mente,INT_MIN
é convertido paraunsigned
), entãox - INT_MIN <= INT_MAX
, não haverá nenhum estouro.Se isso não for óbvio, dê uma olhada na afirmação "Se
x >= -4u
, entãox + 4 <= 3
." E tenha em mente queINT_MAX
será igual a pelo menos o valor matemático de -INT_MIN - 1.Nos sistemas mais comuns, onde
!(x <= INT_MAX)
implicax >= INT_MIN
, o otimizador deve ser capaz (e no meu sistema, é capaz) de remover a segunda verificação, determinar que as duasreturn
instruções podem ser compiladas para o mesmo código e remover a primeira verificação também. Lista de montagem gerada:__Z1fj: LFB6: .cfi_startproc movl 4(%esp), %eax ret .cfi_endproc
A implementação hipotética em sua pergunta:
não é possível, portanto, não requer consideração especial.
INT_MIN
será igual a-INT_MAX
ou a-INT_MAX - 1
. Isso segue da representação do C de tipos inteiros (6.2.6.2), que exige que osn
bits sejam bits de valor, um bit seja um bit de sinal e só permite uma única representação de trap (não incluindo representações que são inválidas por causa de bits de preenchimento), ou seja, aquele que de outra forma representaria zero / negativo-INT_MAX - 1
. C ++ não permite nenhuma representação inteira além do que C permite.Atualização : o compilador da Microsoft aparentemente não percebe isso
x > 10
ex >= 11
testa a mesma coisa. Ele só gera o código desejado sex >= INT_MIN
for substituído porx > INT_MIN - 1u
, que pode detectar como a negação dex <= INT_MAX
(nesta plataforma).[Atualização do questionador (Nemo), elaborando nossa discussão abaixo]
Agora acredito que essa resposta funciona em todos os casos, mas por razões complicadas. É provável que eu concorde com a recompensa por essa solução, mas quero capturar todos os detalhes sangrentos, caso alguém se importe.
Vamos começar com C ++ 11, seção 18.3.3:
Aqui, "Padrão C" significa C99, cuja especificação restringe severamente a representação de inteiros com sinal. Eles são como inteiros sem sinal, mas com um bit dedicado ao "sinal" e zero ou mais bits dedicados ao "preenchimento". Os bits de preenchimento não contribuem para o valor do inteiro e o bit de sinal contribui apenas como complemento de dois, complemento de uns ou magnitude de sinal.
Como o C ++ 11 herda as
<climits>
macros do C99, INT_MIN é -INT_MAX ou -INT_MAX-1 e o código do hvd tem garantia de funcionamento. (Observe que, devido ao preenchimento, INT_MAX pode ser muito menor do que UINT_MAX / 2 ... Mas, graças à maneira como as conversões com sinal-> sem sinal funcionam, esta resposta funciona bem.)C ++ 03 / C ++ 98 é mais complicado. Ele usa o mesmo texto para herdar
<climits>
do "Padrão C", mas agora "Padrão C" significa C89 / C90.Todos estes - C ++ 98, C ++ 03, C89 / C90 - têm a redação que eu forneço na minha pergunta, mas também incluem isto (C ++ 03 seção 3.9.1 parágrafo 7):
A nota de rodapé (44) define "sistema de numeração binária puro":
O que é interessante sobre esse texto é que ele se contradiz, pois a definição de "sistema de numeração binária puro" não permite uma representação de sinal / magnitude! Ele permite que o bit alto tenha, digamos, o valor -2 n-1 (complemento de dois) ou - (2 n-1 -1) (complemento de uns). Mas não há valor para o bit alto que resulta em sinal / magnitude.
De qualquer forma, minha "implementação hipotética" não se qualifica como "binário puro" sob esta definição, portanto, está descartada.
No entanto, o fato de que o bit alto é especial significa que podemos imaginá-lo contribuindo com qualquer valor: Um pequeno valor positivo, grande valor positivo, pequeno valor negativo ou grande valor negativo. (Se o bit de sinal pode contribuir - (2 n-1 -1), por que não - (2 n-1 -2)? Etc.)
Então, vamos imaginar uma representação inteira com sinal que atribui um valor estranho ao bit de "sinal".
Um pequeno valor positivo para o bit de sinal resultaria em um intervalo positivo para
int
(possivelmente tão grande quantounsigned
), e o código de hvd lida com isso muito bem.Um grande valor positivo para o bit de sinal resultaria em
int
um máximo maior queunsigned
, o que é proibido.Um grande valor negativo para o bit de sinal resultaria na
int
representação de uma faixa não contígua de valores, e outras palavras nas especificações excluem isso.Finalmente, que tal um bit de sinal que contribui com uma pequena quantidade negativa? Poderíamos ter um 1 no "bit de sinal" contribuindo, digamos, -37 para o valor do int? Então INT_MAX seria (digamos) 2 31 -1 e INT_MIN seria -37?
Isso resultaria em alguns números com duas representações ... Mas um complemento dá duas representações para zero, e isso é permitido de acordo com o "Exemplo". Em nenhum lugar a especificação diz que zero é o único inteiro que pode ter duas representações. Portanto, acho que essa nova hipótese é permitida pela especificação.
Na verdade, qualquer valor negativo de -1 até
-INT_MAX-1
parece ser permitido como um valor para o "bit de sinal", mas nada menor (para que o intervalo não seja contíguo). Em outras palavras,INT_MIN
pode ser qualquer coisa de-INT_MAX-1
-1.Agora, adivinhe? Para que a segunda conversão no código do hvd evite o comportamento definido pela implementação, precisamos apenas
x - (unsigned)INT_MIN
menor ou igual aINT_MAX
. Acabamos de mostrar queINT_MIN
é pelo menos-INT_MAX-1
. Obviamente,x
é no máximoUINT_MAX
. Lançar um número negativo para não assinado é o mesmo que somarUINT_MAX+1
. Junte tudo:x - (unsigned)INT_MIN <= INT_MAX
se e apenas se
UINT_MAX - (INT_MIN + UINT_MAX + 1) <= INT_MAX -INT_MIN-1 <= INT_MAX -INT_MIN <= INT_MAX+1 INT_MIN >= -INT_MAX-1
Esse último é o que acabamos de mostrar, então, mesmo neste caso perverso, o código realmente funciona.
Isso esgota todas as possibilidades, encerrando assim este exercício extremamente acadêmico.
Resumindo: há algum comportamento seriamente subespecificado para inteiros assinados em C89 / C90 que foi herdado por C ++ 98 / C ++ 03. Ele é corrigido no C99 e o C ++ 11 indiretamente herda a correção ao incorporar
<limits.h>
do C99. Mas mesmo o C ++ 11 mantém a formulação contraditória de "representação binária pura" ...fonte
<limits.h>
são definidos no padrão C ++ como tendo o mesmo significado que no padrão C, portanto, todos os requisitos de C paraINT_MIN
eINT_MAX
são herdados em C ++. Você está correto que C ++ 03 se refere a C90, e C90 é vago sobre as representações inteiras permitidas, mas a mudança C99 (herdada pelo menos via<limits.h>
C ++ 11, esperançosamente também de uma maneira mais direta) para limitá-la a aqueles três eram um que codificava a prática existente: nenhuma outra implementação existia.INT_MIN
etc. são herdados de C. Mas isso não significa que os valores sejam. (Na verdade, como eles poderiam, uma vez que cada implementação é diferente?) Sua inferência queINT_MIN
está dentro de 1 de-INT_MAX
depende de palavras que simplesmente não aparecem em nenhuma especificação C ++. Portanto, embora C ++ herde o significado semântico das macros, a especificação não fornece (ou herda) o texto que apóia sua inferência. Isso parece ser um descuido na especificação C ++ que impede um elenco não assinado para assinado eficiente e totalmente em conformidade.INT_MIN
não é necessário que seja o valor mínimo representável de tipoint
, porque no que diz respeito a C, se o tipo não corresponder aos requisitos deint
, o padrão C não pode cobrir essa implementação de forma alguma, e o padrão C ++ não fornece nenhuma definição além de "o que o padrão C diz". Vou verificar se há uma explicação mais direta.Este código depende apenas do comportamento, exigido pela especificação, portanto, o requisito (a) é facilmente satisfeito:
int unsigned_to_signed(unsigned n) { int result = INT_MAX; if (n > INT_MAX && n < INT_MIN) throw runtime_error("no signed int for this number"); for (unsigned i = INT_MAX; i != n; --i) --result; return result; }
Não é tão fácil com o requisito (b). Isso compila em um no-op com gcc 4.6.3 (-Os, -O2, -O3) e com clang 3.0 (-Os, -O, -O2, -O3). Intel 12.1.0 se recusa a otimizar isso. E não tenho informações sobre Visual C.
fonte
result
estoura; estouro de inteiro é indefinido; portanto, o loop termina; portanto,i == n
na rescisão; portanto,result
é igualn
. Ainda tenho que preferir a resposta do hvd (para o comportamento não patológico em compiladores menos inteligentes), mas isso merece mais votos positivos.n
é algum valor sem sinal e,i
eventualmente, deve atingir todos os valores sem sinal.A resposta original resolveu o problema apenas para
unsigned
=>int
. E se quisermos resolver o problema geral de "algum tipo sem sinal" para seu tipo com sinal correspondente? Além disso, a resposta original foi excelente ao citar seções do padrão e analisar alguns casos extremos, mas realmente não me ajudou a entender por que funcionou, então esta resposta tentará fornecer uma base conceitual forte. Esta resposta tentará ajudar a explicar "por que" e usar recursos modernos do C ++ para tentar simplificar o código.Resposta C ++ 20
O problema foi simplificado drasticamente com P0907: Inteiros assinados são Complemento de Dois e a redação final P1236 que foi votada no padrão C ++ 20. Agora, a resposta é a mais simples possível:
template<std::unsigned_integral T> constexpr auto cast_to_signed_integer(T const value) { return static_cast<std::make_signed_t<T>>(value); }
É isso aí. UMA
static_cast
(ou elenco de estilo C) finalmente terá a garantia de fazer o que você precisa para essa questão, e o que muitos programadores pensaram que sempre faria.Resposta C ++ 17
No C ++ 17, as coisas são muito mais complicadas. Temos que lidar com três possíveis representações inteiras (complemento de dois, complemento de uns e magnitude do sinal). Mesmo no caso em que sabemos que deve ser o complemento de dois, porque verificamos o intervalo de valores possíveis, a conversão de um valor fora do intervalo do inteiro com sinal para esse inteiro com sinal ainda nos dá um resultado definido pela implementação. Temos que usar truques como vimos em outras respostas.
Primeiro, aqui está o código de como resolver o problema genericamente:
template<typename T, typename = std::enable_if_t<std::is_unsigned_v<T>>> constexpr auto cast_to_signed_integer(T const value) { using result = std::make_signed_t<T>; using result_limits = std::numeric_limits<result>; if constexpr (result_limits::min() + 1 != -result_limits::max()) { if (value == static_cast<T>(result_limits::max()) + 1) { throw std::runtime_error("Cannot convert the maximum possible unsigned to a signed value on this system"); } } if (value <= result_limits::max()) { return static_cast<result>(value); } else { using promoted_unsigned = std::conditional_t<sizeof(T) <= sizeof(unsigned), unsigned, T>; using promoted_signed = std::make_signed_t<promoted_unsigned>; constexpr auto shift_by_window = [](auto x) { // static_cast to avoid conversion warning return x - static_cast<decltype(x)>(result_limits::max()) - 1; }; return static_cast<result>( shift_by_window( // shift values from common range to negative range static_cast<promoted_signed>( shift_by_window( // shift large values into common range static_cast<promoted_unsigned>(value) // cast to avoid promotion to int ) ) ) ); } }
Isso tem um pouco mais de conversão do que a resposta aceita, e isso é para garantir que não haja avisos de incompatibilidade assinados / não assinados de seu compilador e para lidar adequadamente com as regras de promoção de inteiros.
Primeiro, temos um caso especial para sistemas que não são complemento de dois (e, portanto, devemos lidar com o valor máximo possível, especialmente porque não tem nada para mapear). Depois disso, chegamos ao algoritmo real.
A segunda condição de nível superior é direta: sabemos que o valor é menor ou igual ao valor máximo, portanto, ele se ajusta ao tipo de resultado. A terceira condição é um pouco mais complicada, mesmo com os comentários, então alguns exemplos provavelmente ajudariam a entender por que cada afirmação é necessária.
Base conceitual: a reta numérica
Primeiro, qual é esse
window
conceito? Considere a seguinte reta numérica:| signed | <.........................> | unsigned |
Acontece que, para inteiros de complemento de dois, você pode dividir o subconjunto da reta numérica que pode ser alcançada por qualquer tipo em três categorias de tamanhos iguais:
- => signed only = => both + => unsigned only <..-------=======+++++++..>
Isso pode ser facilmente comprovado considerando a representação. Um inteiro sem sinal começa em
0
e usa todos os bits para aumentar o valor em potências de 2. Um inteiro com sinal é exatamente o mesmo para todos os bits, exceto o bit de sinal, que vale em-(2^position)
vez de2^position
. Isso significa que, para todos osn - 1
bits, eles representam os mesmos valores. Então, inteiros sem sinal têm mais um bit normal, que dobra o número total de valores (em outras palavras, há tantos valores com aquele bit definido quanto sem ele). A mesma lógica vale para inteiros com sinal, exceto que todos os valores com esse conjunto de bits são negativos.As outras duas representações inteiras legais, complemento de uns e magnitude de sinal, têm todos os mesmos valores que inteiros de complemento de dois, exceto por um: o valor mais negativo. C ++ define tudo sobre tipos inteiros, exceto para
reinterpret_cast
(e C ++ 20std::bit_cast
), em termos de intervalo de valores representáveis, não em termos de representação de bits. Isso significa que nossa análise será válida para cada uma dessas três representações, desde que nunca tentemos criar a representação de armadilha. O valor sem sinal que mapearia para esse valor ausente é bastante infeliz: aquele bem no meio dos valores sem sinal. Felizmente, nossa primeira condição verifica (em tempo de compilação) se tal representação existe e, em seguida, trata-a especialmente com uma verificação de tempo de execução.A primeira condição trata do caso em que estamos na
=
seção, o que significa que estamos na região de sobreposição onde os valores de uma podem ser representados na outra sem alteração. A região) para que tenhamos um mapeamento exclusivo novamente.shift_by_window
função no código move todos os valores para baixo de acordo com o tamanho de cada um desses segmentos (temos que subtrair o valor máximo e depois subtrair 1 para evitar problemas de estouro aritmético). Se estivermos fora dessa região (estamos na+
região), precisamos pular para baixo em um tamanho de janela. Isso nos coloca na faixa de sobreposição, o que significa que podemos converter com segurança de sem sinal para sinal, porque não há alteração no valor. No entanto, ainda não terminamos porque mapeamos dois valores sem sinal para cada valor com sinal. Portanto, precisamos descer para a próxima janela (o-
Agora, isso nos dá um mod congruente de resultado
UINT_MAX + 1
, conforme solicitado na pergunta?UINT_MAX + 1
é equivalente a2^n
, onden
é o número de bits na representação do valor. O valor que usamos para o tamanho da nossa janela é igual a2^(n - 1)
(o índice final em uma sequência de valores é um a menos que o tamanho). Subtraímos esse valor duas vezes, o que significa que subtraímos2 * 2^(n - 1)
que é igual a2^n
. Adicionar e subtrairx
é um modo autônomo no modo aritméticox
, então não afetamos o mod de valor original2^n
.Tratamento adequado de promoções inteiras
Porque esta é uma função genérica e não apenas
int
eunsigned
, também temos que nos preocupar com as regras da promoção integral. Existem dois casos possivelmente interessantes: um em queshort
é menor queint
e outro em queshort
é do mesmo tamanho queint
.Exemplo:
short
menor queint
Se
short
for menor queint
(comum em plataformas modernas), também sabemos queunsigned short
pode caber em umint
, o que significa que todas as operações nele realmente acontecerão emint
, portanto, lançamos explicitamente para o tipo promovido para evitar isso. Nossa declaração final é bastante abstrata e se torna mais fácil de entender se substituirmos por valores reais. Para o nosso primeiro caso interessante, sem perda de generalidade, vamos considerar um 16 bitsshort
e um 17 bitsint
(o que ainda é permitido pelas novas regras, e significaria apenas que pelo menos um desses dois tipos inteiros tem alguns bits de preenchimento ):constexpr auto shift_by_window = [](auto x) { return x - static_cast<decltype(x)>(32767) - 1; }; return static_cast<int16_t>( shift_by_window( static_cast<int17_t>( shift_by_window( static_cast<uint17_t>(value) ) ) ) );
Resolvendo para o maior valor não sinalizado de 16 bits possível
constexpr auto shift_by_window = [](auto x) { return x - static_cast<decltype(x)>(32767) - 1; }; return int16_t( shift_by_window( int17_t( shift_by_window( uint17_t(65535) ) ) ) );
Simplifica para
return int16_t( int17_t( uint17_t(65535) - uint17_t(32767) - 1 ) - int17_t(32767) - 1 );
Simplifica para
return int16_t( int17_t(uint17_t(32767)) - int17_t(32767) - 1 );
Simplifica para
return int16_t( int17_t(32767) - int17_t(32767) - 1 );
Simplifica para
return int16_t(-1);
Colocamos no maior sem assinatura possível e voltamos
-1
, sucesso!Exemplo:
short
mesmo tamanho queint
Se
short
for do mesmo tamanho queint
(incomum em plataformas modernas), a regra de promoção integral é um pouco diferente. Nesse caso,short
promove paraint
eunsigned short
promove paraunsigned
. Felizmente, convertemos explicitamente cada resultado para o tipo em que queremos fazer o cálculo, portanto, acabamos sem promoções problemáticas. Sem perda de generalidade, vamos considerar um 16 bitsshort
e um 16 bitsint
:constexpr auto shift_by_window = [](auto x) { return x - static_cast<decltype(x)>(32767) - 1; }; return static_cast<int16_t>( shift_by_window( static_cast<int16_t>( shift_by_window( static_cast<uint16_t>(value) ) ) ) );
Resolvendo para o maior valor não sinalizado de 16 bits possível
auto x = int16_t( uint16_t(65535) - uint16_t(32767) - 1 ); return int16_t( x - int16_t(32767) - 1 );
Simplifica para
return int16_t( int16_t(32767) - int16_t(32767) - 1 );
Simplifica para
return int16_t(-1);
Colocamos o maior não assinado possível e voltamos
-1
, sucesso!E se eu só se preocupam
int
eunsigned
e não se preocupam com os avisos, como a pergunta original?constexpr int cast_to_signed_integer(unsigned const value) { using result_limits = std::numeric_limits<int>; if constexpr (result_limits::min() + 1 != -result_limits::max()) { if (value == static_cast<unsigned>(result_limits::max()) + 1) { throw std::runtime_error("Cannot convert the maximum possible unsigned to a signed value on this system"); } } if (value <= result_limits::max()) { return static_cast<int>(value); } else { constexpr int window = result_limits::min(); return static_cast<int>(value + window) + window; } }
Ver ao vivo
https://godbolt.org/z/74hY81
Aqui, vemos que clang, gcc e icc não geram código para
cast
ecast_to_signed_integer_basic
em-O2
e-O3
, e MSVC não gera código em/O2
, portanto, a solução é ótima.fonte
Você pode dizer explicitamente ao compilador o que deseja fazer:
int unsigned_to_signed(unsigned n) { if (n > INT_MAX) { if (n <= UINT_MAX + INT_MIN) { throw "no result"; } return static_cast<int>(n + INT_MIN) - (UINT_MAX + INT_MIN + 1); } else { return static_cast<int>(n); } }
Compila com
gcc 4.7.2
forx86_64-linux
(g++ -O -S test.cpp
) parafonte
UINT_MAX
é uma expressão de tipounsigned int
, e isso torna todo o seustatic_cast<int>(n + INT_MIN) - (UINT_MAX + INT_MIN + 1)
tipo. Deve ser possível consertar isso, no entanto, e espero que ainda seja compilado da mesma forma.Se
x
é nossa contribuição ...Se
x > INT_MAX
, queremos encontrar uma constantek
tal que0
<x - k*INT_MAX
<INT_MAX
.Isso é fácil -
unsigned int k = x / INT_MAX;
. Então deixaunsigned int x2 = x - k*INT_MAX;
Agora podemos lançar
x2
comint
segurança. Deixeiint x3 = static_cast<int>(x2);
Agora queremos subtrair algo como
UINT_MAX - k * INT_MAX + 1
dex3
, sek > 0
.Agora, em um sistema de complemento 2s, contanto que
x > INT_MAX
isso funcione para:unsigned int k = x / INT_MAX; x -= k*INT_MAX; int r = int(x); r += k*INT_MAX; r -= UINT_MAX+1;
Observe que
UINT_MAX+1
é garantido zero em C ++, a conversão para int foi um noop e subtraímosk*INT_MAX
e adicionamos de volta no "mesmo valor". Portanto, um otimizador aceitável deve ser capaz de apagar toda aquela tolice!Isso deixa o problema de
x > INT_MAX
ou não. Bem, criamos 2 branches, um comx > INT_MAX
e outro sem. O que não tem faz um lançamento estreito, que o compilador otimiza para um noop. Aquele com ... faz um noop após o otimizador terminar. O otimizador inteligente realiza as duas ramificações na mesma coisa e descarta a ramificação.Problemas: se
UINT_MAX
for realmente grande em relação aINT_MAX
, o acima pode não funcionar. Estou assumindo quek*INT_MAX <= UINT_MAX+1
implicitamente.Provavelmente poderíamos atacar isso com alguns enums como:
enum { divisor = UINT_MAX/INT_MAX, remainder = UINT_MAX-divisor*INT_MAX };
que funcionam para 2 e 1 em um sistema de complemento 2s, eu acredito (temos garantia de que a matemática funcione? Isso é complicado ...), e fazem a lógica com base neles que otimizam facilmente em sistemas de complemento não-2s ...
Isso também abre o caso de exceção. Isso só é possível se UINT_MAX for muito maior que (INT_MIN-INT_MAX), então você pode colocar seu código de exceção em um bloco if perguntando exatamente essa pergunta de alguma forma, e não vai atrasar você em um sistema tradicional.
Não tenho certeza de como construir essas constantes de tempo de compilação para lidar corretamente com isso.
fonte
UINT_MAX
não pode ser pequeno em relação aINT_MAX
, porque a especificação garante que todo int com sinal positivo é representável como um int sem sinal. MasUINT_MAX+1
é zero em todos os sistemas; aritmética sem sinal é sempre móduloUINT_MAX+1
. Ainda pode haver um núcleo de uma abordagem viável aqui ...UINT_MAX+1
é zero em todos os sistemas" estabelecida no '03 -spec? Se sim, há uma subseção específica que eu deveria estar procurando? Obrigado.std::numeric_limits<int>::is_modulo
é uma constante de tempo de compilação. para que você possa usá-lo para especialização de modelo. problema resolvido, pelo menos se o compilador funcionar junto com o inlining.#include <limits> #include <stdexcept> #include <string> #ifdef TESTING_SF bool const testing_sf = true; #else bool const testing_sf = false; #endif // C++ "extensions" namespace cppx { using std::runtime_error; using std::string; inline bool hopefully( bool const c ) { return c; } inline bool throw_x( string const& s ) { throw runtime_error( s ); } } // namespace cppx // C++ "portability perversions" namespace cppp { using cppx::hopefully; using cppx::throw_x; using std::numeric_limits; namespace detail { template< bool isTwosComplement > int signed_from( unsigned const n ) { if( n <= unsigned( numeric_limits<int>::max() ) ) { return static_cast<int>( n ); } unsigned const u_max = unsigned( -1 ); unsigned const u_half = u_max/2 + 1; if( n == u_half ) { throw_x( "signed_from: unsupported value (negative max)" ); } int const i_quarter = static_cast<int>( u_half/2 ); int const int_n1 = static_cast<int>( n - u_half ); int const int_n2 = int_n1 - i_quarter; int const int_n3 = int_n2 - i_quarter; hopefully( n == static_cast<unsigned>( int_n3 ) ) || throw_x( "signed_from: range error" ); return int_n3; } template<> inline int signed_from<true>( unsigned const n ) { return static_cast<int>( n ); } } // namespace detail inline int signed_from( unsigned const n ) { bool const is_modulo = numeric_limits< int >::is_modulo; return detail::signed_from< is_modulo && !testing_sf >( n ); } } // namespace cppp #include <iostream> using namespace std; int main() { int const x = cppp::signed_from( -42u ); wcout << x << endl; }
EDIT : Corrigido o código para evitar uma possível armadilha em máquinas não modulares (apenas uma é conhecida, a saber, as versões configuradas arcaicamente do Unisys Clearpath). Para simplificar, isso é feito não suportando o valor -2 n -1, onde n é o número de
int
bits de valor, nessa máquina (ou seja, no Clearpath). na prática, este valor também não será suportado pela máquina (isto é, com sinal e magnitude ou representação de complemento de 1).fonte
Eu acho que o tipo int é de pelo menos dois bytes, então o INT_MIN e INT_MAX podem mudar em plataformas diferentes.
Tipos fundamentais
≤climits≥ cabeçalho
fonte
Meu dinheiro é usar o memcpy. Qualquer compilador decente sabe como otimizá-lo:
#include <stdio.h> #include <memory.h> #include <limits.h> static inline int unsigned_to_signed(unsigned n) { int result; memcpy( &result, &n, sizeof(result)); return result; } int main(int argc, const char * argv[]) { unsigned int x = UINT_MAX - 1; int xx = unsigned_to_signed(x); return xx; }
Para mim (Xcode 8.3.2, Apple LLVM 8.1, -O3), isso produz:
_main: ## @main Lfunc_begin0: .loc 1 21 0 ## /Users/Someone/main.c:21:0 .cfi_startproc ## BB#0: pushq %rbp Ltmp0: .cfi_def_cfa_offset 16 Ltmp1: .cfi_offset %rbp, -16 movq %rsp, %rbp Ltmp2: .cfi_def_cfa_register %rbp ##DEBUG_VALUE: main:argc <- %EDI ##DEBUG_VALUE: main:argv <- %RSI Ltmp3: ##DEBUG_VALUE: main:x <- 2147483646 ##DEBUG_VALUE: main:xx <- 2147483646 .loc 1 24 5 prologue_end ## /Users/Someone/main.c:24:5 movl $-2, %eax popq %rbp retq Ltmp4: Lfunc_end0: .cfi_endproc
fonte