Por que C não possui carros alegóricos não assinados?

131

Eu sei, a pergunta parece estranha. Os programadores às vezes pensam demais. Por favor, leia em ...

No IC, use signede unsignednúmeros inteiros muito. Eu gosto do fato de o compilador me avisar se eu fizer coisas como atribuir um número inteiro assinado a uma variável não assinada. Recebo avisos se comparar assinados com números inteiros não assinados e muito mais.

Eu gosto desses avisos. Eles me ajudam a manter meu código correto.

Por que não temos o mesmo luxo para carros alegóricos? Uma raiz quadrada definitivamente nunca retornará um número negativo. Também há outros lugares onde um valor flutuante negativo não tem significado. Candidato perfeito para um carro alegórico não assinado.

Btw - Eu não estou realmente interessado no único bit extra de precisão que eu poderia obter removendo o bit de sinal dos carros alegóricos. Estou super feliz com floats como estão agora. Eu gostaria de marcar um float como não assinado às vezes e receber o mesmo tipo de aviso que recebo com números inteiros.

Não conheço nenhuma linguagem de programação que suporte números de ponto flutuante não assinados.

Alguma idéia de por que eles não existem?


EDITAR:

Eu sei que o x87 FPU não tem instruções para lidar com carros alegóricos não assinados. Vamos apenas usar as instruções de flutuação assinadas. O uso indevido (por exemplo, abaixo de zero) pode ser considerado um comportamento indefinido da mesma maneira que o excesso de números inteiros assinados é indefinido.

Nils Pipenbrinck
fonte
4
Interessante, você pode postar um exemplo de caso em que a digitação de assinaturas foi útil?
litb, seu comentário foi direcionado a mim? Se assim for, eu não entendo
Iraimbilanja yeah :) fabs não pode retornar um número negativo, porque retorna o valor absoluto de seu argumento
Johannes Schaub - litb 4/09/09
Certo. Não perguntei como um flutuador hipotético não assinado poderia ajudar a correção. O que eu perguntei foi: em que situação o pipenbrinck achou útil a tipificação de sinalização Int (levando-o a procurar o mesmo mecanismo para carros alegóricos). com relação a typesafety
1
Há uma micro-otimização não assinada para verificação point-in-range: ((não assinada) (p-min)) <(max-min), que possui apenas uma ramificação, mas, como sempre, é melhor criar um perfil para verificar se realmente ajuda (usei principalmente em 386 núcleos, então não sei como as CPUs modernas lidam).
515 Skizz

Respostas:

114

Por que o C ++ não tem suporte para flutuadores não assinados é porque não há operações equivalentes de código de máquina para a CPU executar. Portanto, seria muito ineficiente apoiá-lo.

Se o C ++ o suportasse, você às vezes usaria um flutuador não assinado e não perceberia que seu desempenho acabou de ser morto. Se o C ++ o suportasse, todas as operações de ponto flutuante precisariam ser verificadas para ver se estão assinadas ou não. E para programas que realizam milhões de operações de ponto flutuante, isso não é aceitável.

Portanto, a pergunta seria por que os implementadores de hardware não o suportam. E acho que a resposta é que não havia um padrão de flutuação sem sinal definido originalmente. Como os idiomas gostam de ser compatíveis com versões anteriores, mesmo que fossem adicionados, os idiomas não poderiam fazer uso dele. Para ver a especificação de ponto flutuante, consulte o ponto flutuante padrão IEEE 754 .

Você pode evitar um tipo de ponto flutuante não assinado criando uma classe de flutuação não assinada que encapsule um float ou double e emita avisos se você tentar passar um número negativo. Isso é menos eficiente, mas provavelmente se você não os estiver usando intensamente, não se importará com essa pequena perda de desempenho.

Definitivamente, vejo a utilidade de ter um carro alegórico não assinado. Mas o C / C ++ tende a escolher a eficiência que funciona melhor para todos que a segurança.

Brian R. Bondy
fonte
17
O C / C ++ não requer operações específicas de código de máquina para implementar o idioma. Os primeiros compiladores C / C ++ podiam gerar código de ponto flutuante para o 386 - uma CPU sem FPU! O compilador geraria chamadas de biblioteca para emular instruções de FPU. Portanto, um ufloat é poderia ser feito sem o apoio CPU
Skizz
10
Skizz, enquanto isso está correto, Brian já abordou isso - que, como não há código de máquina equivalente, o desempenho será horrível em comparação.
29740 Anthony
2
@ Brian R. Bondy: Eu te perdi aqui: "porque não há operações de código de máquina equivalentes para a CPU executar ...". Você pode explicar, em termos mais simples?
21710 Lazer
2
O motivo pelo qual o OP queria suporte para carros alegóricos não assinados era por mensagens de aviso, então, na verdade, nada tem a ver com a fase de geração de código do compilador - apenas com a maneira como ele faz a verificação de tipo antes - portanto, o suporte a eles no código de máquina é irrelevante. e (como foi adicionado ao final da pergunta) instruções normais de ponto flutuante podem ser usadas para execução real.
Joe F
2
Não sei ao certo por que isso deve afetar o desempenho. Assim como intacontece com todas as verificações de tipo relacionadas a sinais, podem ocorrer em tempo de compilação. O OP sugere que isso unsigned floatseria implementado regularmente floatcom verificações em tempo de compilação para garantir que determinadas operações não significativas nunca sejam executadas. O código da máquina e o desempenho resultantes podem ser idênticos, independentemente de seus carros alegóricos serem assinados ou não.
precisa saber é o seguinte
14

Há uma diferença significativa entre números inteiros assinados e não assinados em C / C ++:

value >> shift

valores assinados deixam o bit superior inalterado (extensão do sinal), valores não assinados limpam o bit superior.

O motivo pelo qual não há flutuação não assinada é que você encontra rapidamente todos os tipos de problemas se não houver valores negativos. Considere isto:

float a = 2.0f, b = 10.0f, c;
c = a - b;

Qual valor c tem? -8 Mas o que isso significaria em um sistema sem números negativos. FLOAT_MAX - 8 talvez? Na verdade, isso não funciona como FLOAT_MAX - 8 é FLOAT_MAX devido a efeitos de precisão, portanto as coisas são ainda mais complicadas. E se fosse parte de uma expressão mais complexa:

float a = 2.0f, b = 10.0f, c = 20.0f, d = 3.14159f, e;
e = (a - b) / d + c;

Isso não é um problema para números inteiros devido à natureza do sistema de complemento do 2.

Considere também as funções matemáticas padrão: sin, cos e tan funcionariam apenas pela metade dos valores de entrada, você não poderia encontrar o log de valores <1, não conseguiria resolver equações quadráticas: x = (-b +/- root ( bb - 4.ac)) / 2.a e assim por diante. De fato, provavelmente não funcionaria para nenhuma função complexa, pois elas tendem a ser implementadas como aproximações polinomiais que usariam valores negativos em algum lugar.

Portanto, carros alegóricos não assinados são bastante inúteis.

Mas isso não significa dizer que uma classe que alcance valores flutuantes não seja útil; convém fixar valores em um determinado intervalo, por exemplo, cálculos RGB.

Skizz
fonte
@ Skizz: se a representação é um problema, você quer dizer que alguém possa inventar um método para armazenar carros alegóricos tão eficiente quanto 2's complement, não haverá problema em ter carros alegóricos não assinados?
21410 Lazer
3
value >> shift for signed values leave the top bit unchanged (sign extend) Você tem certeza sobre isso? Eu pensei que era um comportamento definido pela implementação, pelo menos para valores assinados negativos.
Dan
@ Dan: Acabei de olhar para o padrão recente e, de fato, afirma que está definido para implementação - acho que é apenas no caso de haver uma CPU que não tem nenhum turno certo com instruções de extensão de sinal.
Skizz
1
ponto flutuante tradicionalmente satura (para - / + Inf) em vez de quebra automática. Você pode esperar que o estouro de subtração não assinado sature para 0.0, ou possivelmente Inf ou NaN. Ou apenas seja Comportamento indefinido, como o OP sugerido em uma edição da pergunta. Re: funções trig: por isso não defina versões de entrada não assinada sine assim por diante, e certifique-se de tratar seu valor de retorno como assinado. A pergunta não estava propondo substituir float por float não assinado, apenas adicionando unsigned floatcomo um novo tipo.
Peter Cordes
9

(Além disso, o Perl 6 permite escrever

subset Nonnegative::Float of Float where { $_ >= 0 };

e então você pode usar Nonnegative::Floatcomo faria com qualquer outro tipo.)

Não há suporte de hardware para operações de ponto flutuante não assinadas, portanto, o C não o oferece. C é projetado principalmente para ser "montagem portátil", ou seja, o mais próximo possível do metal, sem ser amarrado a uma plataforma específica.

[editar]

C é como montagem: o que você vê é exatamente o que você recebe. Um implícito "Vou verificar se esse float não é negativo para você" vai contra a sua filosofia de design. Se você realmente deseja, pode adicionar assert(x >= 0)ou semelhante, mas você deve fazer isso explicitamente.

efémero
fonte
svn.perl.org/parrot/trunk/languages/perl6/docs/STATUS diz que sim, mas of ...não analisa.
519 ephemient
8

Acredito que o int não assinado foi criado devido à necessidade de uma margem de valor maior do que o int assinado poderia oferecer.

Um flutuador tem uma margem muito maior, portanto, nunca houve uma necessidade "física" de um flutuador não assinado. E, como você se destaca na sua pergunta, a precisão adicional de 1 bit não é motivo para matar.

Edit: Depois de ler a resposta de Brian R. Bondy , tenho que modificar minha resposta: ele está definitivamente certo que as CPUs subjacentes não tiveram operações de flutuação não assinadas. No entanto, mantenho minha convicção de que essa foi uma decisão de design com base nos motivos que afirmei acima ;-)

Treb
fonte
2
Além disso, a adição e subtração de números inteiros é o mesmo ponto flutuante assinado ou não assinado - nem tanto. Quem faria o trabalho extra para apoiar os carros alegóricos assinados e não assinados, dada a utilidade marginal relativamente baixa desse recurso?
ephemient
7

Eu acho que Treb está no caminho certo. É mais importante para números inteiros que você tenha um tipo correspondente não assinado. Esses são os que são usados ​​na troca de bits e nos mapas de bits . Um pouco de sinal entra no caminho. Por exemplo, deslocando à direita um valor negativo, o valor resultante é a implementação definida em C ++. Fazer isso com um número inteiro sem sinal ou transbordando, define semântica perfeitamente definida, porque não existe esse caminho.

Portanto, para números inteiros, pelo menos, a necessidade de um tipo não assinado separado é mais forte do que apenas dar avisos. Todos os pontos acima não precisam ser considerados para carros alegóricos. Portanto, acho que não há necessidade real de suporte de hardware para eles, e C já não os apoiará nesse momento.

Johannes Schaub - litb
fonte
5

Uma raiz quadrada definitivamente nunca retornará um número negativo. Também há outros lugares onde um valor flutuante negativo não tem significado. Candidato perfeito para um carro alegórico não assinado.

O C99 suporta números complexos e uma forma genérica do tipo sqrt, portanto, sqrt( 1.0 * I)será negativo.


Os comentaristas destacaram um leve brilho acima, pois eu estava me referindo à sqrtmacro genérica do tipo, e não à função, e ele retornará um valor escalar de ponto flutuante truncando o complexo para seu componente real:

#include <complex.h>
#include <tgmath.h>

int main () 
{
    complex double a = 1.0 + 1.0 * I;

    double f = sqrt(a);

    return 0;
}

Ele também contém um peido cerebral, pois a parte real do sqrt de qualquer número complexo é positiva ou zero e sqrt (1,0 * I) é sqrt (0,5) + sqrt (0,5) * I não -1,0.

Pete Kirkham
fonte
Sim, mas você chama uma função com um nome diferente se trabalhar com números complexos. Além disso, o tipo de retorno é diferente. Bom ponto!
Nils Pipenbrinck 04/02/09
4
O resultado do sqrt (i) é um número complexo. E uma vez que os números complexos não são ordenados, você não pode dizer um número complexo é negativ (ie <0)
quinmars
1
quinmars, com certeza não é csqrt? ou você fala sobre matemática em vez de C? eu concordo qualquer maneira que é um ponto bom :)
Johannes Schaub - litb
Na verdade, eu estava falando sobre matemática. Eu nunca lidei com números complexos em c.
quinmars 04/02
1
"raiz quadrada definitivamente nunca retornará um número negativo." -> sqrt(-0.0)freqüentemente produz -0.0. Claro que -0,0 não é um valor negativo .
chux - Restabelece Monica
4

Eu acho que depende que as especificações de ponto flutuante IEEE sejam assinadas apenas e que a maioria das linguagens de programação as use.

Artigo da Wikipedia sobre números de ponto flutuante IEEE-754

Edit: Além disso, como observado por outros, a maioria dos hardwares não suporta flutuadores não negativos, portanto, o tipo normal de flutuadores é mais eficiente, pois existe suporte de hardware.

Tobias Wärre
fonte
C foi introduzido muito antes de o padrão IEEE-754 aparecer
phuclv 19/02/19
@phuclv Nem o hardware de ponto flutuante comum. Foi adotado no padrão C "alguns" anos depois. Provavelmente existe alguma documentação flutuando na Internet sobre isso. (Além disso, o artigo da Wikipedia menciona C99).
Tobias Wärre 12/03/19
Eu não entendo o que você quer dizer. Não há nenhuma "hardware" em sua resposta, e IEEE-754 nasceu depois C, então tipos de ponto flutuante em C não pode depender IEEE-754 padrão, a menos que esses tipos foi introduzido em C muito mais tarde
phuclv
O @phuclv C também é conhecido como montagem portátil, por isso pode estar bem próximo do hardware. Idiomas ganha recursos ao longo dos anos, mesmo que (antes do meu tempo) a flutuação tenha sido implementada em C, provavelmente era uma operação baseada em software e bastante cara. Na hora de responder a essa pergunta, eu obviamente tinha uma melhor compreensão do que estava tentando explicar do que agora. E se você olhar para a resposta aceita, poderá entender por que mencionei o padrão IEE754. O que eu não entendo é que você escolheu uma resposta de 10 anos que não é a aceita?
Tobias Wärre 25/03/19
3

Penso que a principal razão é que os flutuadores não assinados teriam usos realmente limitados em comparação com ints não assinados. Não acredito no argumento de que é porque o hardware não suporta. Os processadores mais antigos não tinham nenhuma capacidade de ponto flutuante; tudo era emulado em software. Se flutuadores não assinados fossem úteis, eles seriam implementados primeiro no software e o hardware seguiria o exemplo.

Ferruccio
fonte
4
O PDP-7, a primeira plataforma do C, tinha uma unidade opcional de ponto flutuante de hardware. O PDP-11, a próxima plataforma do C, tinha flutuadores de 32 bits em hardware. 80x86 veio uma geração depois, com alguma tecnologia que estava uma geração atrás.
519 ephemient
3

Tipos inteiros não assinados em C são definidos de maneira a obedecer às regras de um anel algébrico abstrato. Por exemplo, para qualquer valor X e Y, adicionar XY a Y produzirá X. É garantido que os tipos inteiros não assinados obedeçam a essas regras em todos os casos que não envolvam conversão para ou de qualquer outro tipo numérico [ou tipos não assinados de tamanhos diferentes] , e essa garantia é um dos recursos mais importantes desses tipos. Em alguns casos, vale a pena renunciar à capacidade de representar números negativos em troca das garantias extras que somente os tipos não assinados podem fornecer. Tipos de ponto flutuante, assinados ou não, não podem cumprir todas as regras de um anel algébrico [por exemplo, eles não podem garantir que X + YY será igual a X] e, na verdade, o IEEE não ' até permite que eles cumpram as regras de uma classe de equivalência [exigindo que certos valores se comparem desiguais]. Eu não acho que um tipo de ponto flutuante "não assinado" possa respeitar quaisquer axiomas que um tipo de ponto flutuante comum não possa, por isso não tenho certeza de quais vantagens ele ofereceria.

supercat
fonte
1

IHMO, é porque o suporte a tipos de ponto flutuante assinados e não assinados em hardware ou software seria muito problemático

Para tipos inteiros, podemos utilizar a mesma unidade lógica para operações inteiras assinadas e não assinadas na maioria das situações, usando a propriedade nice do complemento 2, porque o resultado é idêntico nesses casos para add, sub, mul não-alargamento e operações bit a bit. Para operações que diferenciam entre versão assinada e não assinada, ainda podemos compartilhar a maioria da lógica . Por exemplo

  • A mudança aritmética e lógica precisa apenas de uma ligeira alteração no preenchimento para os bits superiores
  • A multiplicação de ampliação pode usar o mesmo hardware para a parte principal e, em seguida, alguma lógica separada para ajustar o resultado e alterar a sinalização . Não que seja usado em multiplicadores reais, mas é possível fazer
  • A comparação assinada pode ser convertida em comparação não assinada e vice-versa facilmente, alternando o bit superior ou adicionandoINT_MIN . Também teoricamente possível, provavelmente não é usado em hardware, mas é útil em sistemas que suportam apenas um tipo de comparação (como 8080 ou 8051)

Os sistemas que usam o complemento 1 também precisam de uma pequena modificação na lógica, porque é simplesmente o bit de transporte envolvido no bit menos significativo. Não tenho certeza sobre os sistemas de magnitude de sinal, mas parece que eles usam o complemento de 1 internamente, então o mesmo se aplica

Infelizmente , não temos esse luxo para os tipos de ponto flutuante. Simplesmente liberando o bit de sinal, teremos a versão não assinada. Mas então para que devemos usar esse pedaço?

  • Aumente o intervalo adicionando-o ao expoente
  • Aumente a precisão adicionando-a à mantissa. Isso geralmente é mais útil, pois geralmente precisamos de mais precisão do que o alcance

Mas ambas as opções precisam de um somador maior para acomodar a faixa de valor mais ampla. Isso aumenta a complexidade da lógica enquanto o bit superior do somador fica lá sem uso na maioria das vezes. Mais circuitos serão necessários para multiplicações, divisões ou outras operações complexas

Em sistemas que usam ponto flutuante de software, você precisa de 2 versões para cada função que não era esperada durante o tempo em que a memória era muito cara ou seria necessário encontrar uma maneira "complicada" de compartilhar partes das funções assinadas e não assinadas

No entanto , o hardware de ponto flutuante existia muito antes de C ser inventado , então acredito que a escolha em C foi devido à falta de suporte de hardware devido ao motivo mencionado acima

Dito isto, existe várias especializados formatos sem assinatura de ponto flutuante, principalmente para fins de processamento de imagem, como o tipo de ponto flutuante 10 e 11-bit do grupo Khronos

phuclv
fonte
0

Eu suspeito que é porque os processadores subjacentes direcionados por compiladores C não têm uma boa maneira de lidar com números de pontos flutuantes não assinados.

Brian Ensink
fonte
Os processadores subjacentes tinham uma boa maneira de lidar com números de ponto flutuante assinados? C estava ficando popular quando os processadores auxiliares de ponto flutuante eram idiossincráticos e dificilmente universais.
18740 David Thornley
1
Não conheço todas as linhas do tempo históricas, mas havia um suporte emergente de hardware para carros alegóricos assinados, embora raro como você aponta. Os designers de idiomas podem incorporar suporte a ele, enquanto os back-ends do compilador têm níveis variados de suporte, dependendo da arquitetura de destino.
Brian Ensink
0

Boa pergunta.

Se, como você diz, é apenas para avisos em tempo de compilação e nenhuma alteração no comportamento deles, o hardware subjacente não é afetado e, como tal, seria apenas uma alteração do C ++ / Compilador.

Já ganhei o mesmo anteriormente, mas a questão é: não ajudaria muito. Na melhor das hipóteses, o compilador pode encontrar atribuições estáticas.

unsigned float uf { 0 };
uf = -1f;

Ou minimamente mais longo

unsigned float uf { 0 };
float f { 2 };
uf -= f;

Mas é isso aí. Com tipos inteiros não assinados, você também obtém uma envolvente definida, a saber, que se comporta como aritmética modular.

unsigned char uc { 0 };
uc -= 1;

depois disso 'uc' mantém o valor 255.

Agora, o que um compilador faria com o mesmo cenário, dado um tipo de flutuação não assinado? Se os valores não forem conhecidos no momento da compilação, será necessário gerar o código que primeiro executa os cálculos e, em seguida, faz uma verificação de sinal. Mas e quando o resultado de tal cálculo seria "-5,5" - qual valor deve ser armazenado em um float declarado sem sinal? Pode-se tentar aritmética modular como para tipos integrais, mas isso vem com seus próprios problemas: O maior valor é indiscutivelmente infinito .... isso não funciona, você não pode ter "infinito - 1". Indo para o maior valor distinto que ele pode conter também não funcionará realmente, pois você encontra a precissão. "NaN" seria um candidato.

Por fim, isso não seria um problema com números de pontos fixos, pois o módulo está bem definido.

ABaumstumpf
fonte