Por que 0 é falso?

110

Essa pergunta pode parecer burra, mas por que 0 avalia false e qualquer outro valor [inteiro] para true é a maioria das linguagens de programação?

Comparação de sequências

Como a pergunta parece um pouco simples demais, vou me explicar um pouco mais: em primeiro lugar, pode parecer evidente para qualquer programador, mas por que não haveria uma linguagem de programação - pode realmente haver, mas não usei nenhum - onde 0 avalia para true e todos os outros valores [integer] para false ? Essa observação pode parecer aleatória, mas tenho alguns exemplos em que pode ter sido uma boa ideia. Antes de mais nada, vamos pegar o exemplo da comparação tridimensional de strings, vou usar o strcmp de C como exemplo: qualquer programador que tente C como sua primeira linguagem pode ser tentado a escrever o seguinte código:

if (strcmp(str1, str2)) { // Do something... }

Como strcmp retorna 0 , que é avaliado como false quando as strings são iguais, o que o programador iniciante tentou fazer falhar miseravelmente e ele geralmente não entende por que a princípio. Tinha 0 avaliado como true , essa função poderia ter sido usada em sua expressão mais simples - a acima - ao comparar por igualdade, e as verificações adequadas para -1 e 1 teriam sido feitas somente quando necessário. Teríamos considerado o tipo de retorno como bool (em nossas mentes, quero dizer) na maioria das vezes.

Além disso, vamos introduzir um novo tipo, sign , que apenas recebe valores -1 , 0 e 1 . Isso pode ser muito útil. Imagine que há um operador de espaçonave em C ++ e nós queremos isso para std::string (bem, já existe o compare função, mas operador de espaçonave é mais divertido). A declaração seria atualmente a seguinte:

sign operator<=>(const std::string& lhs, const std::string& rhs);

O 0 foi avaliado como true , o operador da espaçonave nem existiria e poderíamos ter declarado operator== dessa maneira:

sign operator==(const std::string& lhs, const std::string& rhs);

Este operator== teria lidado com a comparação de três vias de uma só vez, e ainda poderia ser usado para realizar a seguinte verificação enquanto ainda seria capaz de verificar qual cadeia é lexicograficamente superior à outra quando necessário:

if (str1 == str2) { // Do something... }

Erros antigos que manipulam

Agora temos exceções, portanto, essa parte só se aplica aos idiomas antigos em que não existe tal coisa (C, por exemplo). Se olharmos para a biblioteca padrão do C (e também para o POSIX), podemos ver com certeza que as funções maaaaany retornam 0 quando bem sucedidas e qualquer número inteiro caso contrário. Eu infelizmente vi algumas pessoas fazerem esse tipo de coisa:

#define TRUE 0
// ...
if (some_function() == TRUE)
{
    // Here, TRUE would mean success...
    // Do something
}

Se pensarmos em como pensamos na programação, geralmente temos o seguinte padrão de raciocínio:

Do something
Did it work?
Yes ->
    That's ok, one case to handle
No ->
    Why? Many cases to handle

Se pensarmos sobre isso novamente, faria sentido colocar o único valor neutro, 0 , em yes (e é assim que funcionam as funções de C), enquanto todos os outros valores podem estar lá para resolver o problema. muitos casos do no . No entanto, em todas as linguagens de programação que eu conheço (exceto talvez algumas linguagens esotéricas experimentais), que yes avalia false em uma condição if , enquanto todos os no casos são avaliados como true . Há muitas situações em que "funciona" representa um caso enquanto "não funciona" representa muitas causas prováveis. Se pensarmos dessa forma, ter 0 avaliado em true e o restante em false teria feito muito mais sentido.

Conclusão

Minha conclusão é essencialmente a minha pergunta original: por que projetamos idiomas em que 0 é false e os outros valores são true , levando em conta meus poucos exemplos acima e talvez um pouco mais eu não pensei?

Acompanhamento: É bom ver que há muitas respostas com muitas ideias e tantas possíveis razões para que seja assim. Eu amo o quão apaixonado você parece ser sobre isso. Eu originalmente fiz essa pergunta de tédio, mas desde que você parece tão apaixonada, eu decidi ir um pouco mais longe e perguntar sobre o raciocínio por trás da escolha booleana para 0 e 1 em Math.SE:)

    
por Morwenn 15.05.2013 / 21:37
fonte

14 respostas

97

0 é false porque ambos são elementos zero em semirings comuns. Mesmo que sejam tipos de dados distintos, faz sentido intuitivo converter entre eles, porque eles pertencem a estruturas algébricas isomórficas.

  • 0 é a identidade para adição e zero para multiplicação. Isso vale para números inteiros e racionais, mas não números de ponto flutuante IEEE-754: 0.0 * NaN = NaN e 0.0 * Infinity = NaN .

  • false é a identidade para Boolean xor (⊻) e zero para Boolean e (∧). Se Booleanos são representados como {0, 1} - o conjunto de inteiros módulo 2 - você pode pensar em ⊻ como adição sem transportar e ∧ como multiplicação.

  • "" e [] são identidade para concatenação, mas existem várias operações para as quais elas fazem sentido como zero. A repetição é uma, mas a repetição e a concatenação não são distribuídas, portanto, essas operações não formam um seminário.

Tais conversões implícitas são úteis em pequenos programas, mas no geral podem tornar os programas mais difíceis de avaliar. Apenas uma das muitas compensações no design de linguagem.

    
por 16.05.2013 / 05:47
fonte
74

Porque a matemática funciona.

FALSE OR TRUE is TRUE, because 0 | 1 is 1.

... insert many other examples here.

Tradicionalmente, os programas em C têm condições como

if (someFunctionReturningANumber())

em vez de

if (someFunctionReturningANumber() != 0)

porque o conceito de zero ser equivalente a falso é bem entendido.

    
por 15.05.2013 / 21:52
fonte
38

Como outros já disseram, a matemática veio primeiro. É por isso que 0 é false e 1 é true .

De qual matemática estamos falando? Álgebras booleanas que datam de meados do século XIX, muito antes dos computadores digitais surgirem.

Você também pode dizer que a convenção surgiu da lógica proposicional , que é ainda mais antiga que as álgebras booleanas. Esta é a formalização de muitos dos resultados lógicos que os programadores conhecem e adoram ( false || x é igual a x , true && x é igual a x e assim por diante).

Basicamente, estamos falando de aritmética em um conjunto com dois elementos. Pense em contar em binário. As álgebras booleanas são a origem deste conceito e sua base teórica. As convenções de linguagens como C são apenas uma aplicação direta.

    
por 15.05.2013 / 22:08
fonte
26

Eu pensei que isso tinha a ver com a "herança" da eletrônica, e também a álgebra booleana, onde

  • 0 = off , negative , no , false
  • 1 = on , positive , yes , true

strcmp retorna 0 quando strings iguais têm a ver com sua implementação, pois o que ele realmente faz é calcular a "distância" entre as duas strings. Esse 0 também passa a ser considerado falso é apenas uma coincidência.

retornando 0 no sucesso faz sentido porque 0 neste caso é usado para significar nenhum erro e qualquer outro número seria um código de erro. Usar qualquer outro número para obter sucesso faria menos sentido, já que você só tem um único código de sucesso, enquanto você pode ter vários códigos de erro. Você usa "funcionou?" como a expressão if e digamos 0 = yes faria mais sentido, mas a expressão é mais corretamente "Alguma coisa deu errado?" e então você vê que 0 = não faz muito sentido. Pensar em false/true não faz muito sentido aqui, pois na verdade é no error code/error code .

    
por 16.05.2013 / 12:44
fonte
18

Conforme explicado em este artigo , os valores false e true não devem ser confundidos com os inteiros 0 e 1, mas pode ser identificado com os elementos do campo de Galois (campo finito) de dois elementos (veja aqui ).

Um campo é um conjunto com duas operações que satisfazem certos axiomas.

Os símbolos 0 e 1 são usados convencionalmente para denotar as identidades aditiva e multiplicativa de um campo, porque os números reais também são um campo (mas não um finito) cujas identidades são os números 0 e 1.

A identidade aditiva é o elemento 0 do campo, de tal forma que para todo x:

x + 0 = 0 + x = x

e a identidade multiplicativa é o elemento 1 do campo, tal que para todo x:

x * 1 = 1 * x = x

O campo finito de dois elementos tem apenas esses dois elementos, ou seja, a identidade aditiva 0 (ou false ) e a identidade multiplicativa 1 (ou true ). As duas operações deste campo são o XOR lógico (+) e o AND lógico (*).

Observação. Se você inverter as operações (XOR é a multiplicação e AND é a adição), a multiplicação não é mais distribuída do que a adição e você não tem mais um campo. Nesse caso, você não tem razão para chamar os dois elementos 0 e 1 (em qualquer ordem). Note também que você não pode escolher a operação OU ao invés de XOR: não importa como você interpreta OR / AND como adição / multiplicação, a estrutura resultante não é um campo (nem todos os elementos inversos existem como requerido pelos axiomas de campo). >

Em relação às funções C:

  • Muitas funções retornam um inteiro que é um código de erro. 0 significa NÃO ERRO.
  • Intuitivamente, a função strcmp calcula a diferença entre duas cadeias. 0 significa que não há diferença entre duas strings, ou seja, que duas strings são iguais.

As explicações intuitivas acima podem ajudar a lembrar a interpretação dos valores de retorno, mas é ainda mais fácil verificar a documentação da biblioteca.

    
por 16.05.2013 / 00:21
fonte
13

Você deve considerar que sistemas alternativos também podem ser decisões de design aceitáveis.

Shells: 0 status de saída é verdadeiro, diferente de zero é falso

O exemplo de shells tratando um status de saída 0 como verdadeiro já foi mencionado.

$ ( exit 0 ) && echo "0 is true" || echo "0 is false"
0 is true
$ ( exit 1 ) && echo "1 is true" || echo "1 is false"
1 is false

A lógica é que existe uma maneira de ter sucesso, mas muitas maneiras de falhar, então usar 0 como o valor especial que significa "sem erros" é pragmático.

Ruby: 0 é como qualquer outro número

Entre as linguagens de programação "normais", existem alguns outliers, como Ruby, que tratam 0 como um valor verdadeiro.

$ irb
irb(main):001:0> 0 ? '0 is true' : '0 is false'
=> "0 is true"

O raciocínio é que apenas false e nil devem ser falsos. Para muitos novatos Ruby, é uma pegadinha. No entanto, em alguns casos, é bom que 0 seja tratado como qualquer outro número.

irb(main):002:0> (pos = 'axe' =~ /x/) ? "Found x at position #{pos}" : "x not found"
=> "Found x at position 1"
irb(main):003:0> (pos = 'xyz' =~ /x/) ? "Found x at position #{pos}" : "x not found"
=> "Found x at position 0"
irb(main):004:0> (pos = 'abc' =~ /x/) ? "Found x at position #{pos}" : "x not found"
=> "x not found"

No entanto, esse sistema funciona apenas em uma linguagem que é capaz de distinguir os booleanos como um tipo separado dos números. Nos primeiros dias da computação, os programadores que trabalhavam com linguagem de montagem ou linguagem bruta de máquina não tinham tais luxos. É provavelmente natural tratar 0 como o estado "em branco" e definir um bit como sinalizador quando o código detectou que algo aconteceu. Por extensão, a convenção desenvolveu que zero foi tratado como falso, e valores diferentes de zero passaram a ser tratados como verdadeiros. No entanto, não precisa ser assim.

Java: os números não podem ser tratados como booleanos

Em Java, true e false são os únicos valores booleanos. Os números não são booleanos nem podem ser convertidos em booleanos ( Java Especificação da linguagem, seção 4.2.2 ):

There are no casts between integral types and the type boolean.

Essa regra simplesmente evita a questão - todas as expressões booleanas devem ser explicitamente escritas no código.

    
por 10.04.2014 / 20:54
fonte
8

Antes de abordar o caso geral, podemos discutir seus exemplos de contador.

Comparações de strings

O mesmo vale para muitos tipos de comparações, na verdade. Essas comparações calculam uma distância entre dois objetos. Quando os objetos são iguais, a distância é mínima. Então, quando a "comparação tiver sucesso", o valor é 0. Mas, na verdade, o valor de retorno de strcmp não é booleano, é uma distância e o que intercepta programadores desconhecidos fazendo if (strcmp(...)) do_when_equal() else do_when_not_equal() .

Em C ++, podemos redesenhar strcmp para retornar um objeto Distance , que substitui operator bool() para retornar true quando 0 (mas você seria então mordido por um conjunto diferente de problemas). Ou, na linguagem C, basta ter uma função streq que retorna 1 quando as strings são iguais e 0 caso contrário.

Chamadas de API / código de saída do programa

Aqui, você se preocupa com o motivo pelo qual algo deu errado, porque isso levará as decisões a um erro. Quando as coisas têm sucesso, você não quer saber nada em particular - sua intenção é realizada. O valor de retorno deve, portanto, transmitir essa informação. Não é um booleano, é um código de erro. O valor de erro especial 0 significa "nenhum erro". O restante do intervalo representa erros significativos com os quais você precisa lidar (incluindo 1, o que geralmente significa "erro não especificado").

Caso geral

Isso nos deixa com a pergunta: por que os valores booleanos True e False são comumente representados com 1 e 0, respectivamente?

Bem, além do argumento subjetivo "é melhor assim", aqui estão algumas razões (subjetivas também) em que posso pensar:

  • analogia do circuito elétrico. A corrente é ON para 1s e OFF para 0s. Eu gosto de ter (1, Sim, True, On) juntos, e (0, Não, False, Off), ao invés de outro mix

  • inicializações de memória. Quando eu memset(0) um monte de variáveis (seja eles ints, floats, bools) eu quero que seu valor corresponda às suposições mais conservadoras. Por exemplo. minha soma é inicialmente 0, o predicado é falso, etc.

Talvez todas essas razões estejam ligadas à minha educação - se eu tivesse sido ensinado a associar 0 com True desde o começo, eu iria pelo caminho inverso.

    
por 15.05.2013 / 23:23
fonte
6

De uma perspectiva de alto nível, você está falando sobre três tipos de dados bem diferentes:

  1. Um booleano. A convenção matemática na álgebra booleana é usar 0 para false e 1 para true , então faz sentido siga essa convenção. Eu acho que isso também faz mais sentido intuitivamente.

  2. O resultado da comparação. Isso tem três valores: < , = e > (observe que nenhum deles é true ). Para eles, faz sentido usar os valores de -1, 0 e 1, respectivamente (ou, mais geralmente, um valor negativo, zero e um valor positivo).

    Se você quiser verificar a igualdade e você só tem uma função que executa comparação geral, acho que você deve explicitar usando algo como strcmp(str1, str2) == 0 . Eu acho que usar ! nessa situação é confuso, porque ele trata um valor não-booleano como se fosse um booleano.

    Além disso, tenha em mente que comparação e igualdade não precisam ser a mesma coisa. Por exemplo, se você pedir as pessoas pela data de nascimento, Compare(me, myTwin) deve retornar 0 , mas Equals(me, myTwin) deve retornar false .

  3. O sucesso ou o fracasso de uma função, possivelmente também com detalhes sobre esse sucesso ou falha. Se você está falando sobre o Windows, esse tipo é chamado de HRESULT e um valor diferente de zero não necessariamente indicar falha. De fato, um valor negativo indica falha e sucesso não negativo. O valor de sucesso é muitas vezes S_OK = 0 , mas também pode ser, por exemplo, S_FALSE = 1 ou outros valores.

A confusão vem do fato de que três tipos de dados logicamente bem diferentes são realmente representados como um único tipo de dados (um inteiro) em C e algumas outras linguagens e que você pode usar inteiro em uma condição. Mas eu não acho que faria sentido redefinir o booleano para tornar o uso de alguns tipos não-booleanos em condições mais simples.

Além disso, considere outro tipo que é frequentemente usado em uma condição em C: um ponteiro. Lá, é natural tratar um NULL -pointer (que é representado como 0 ) como false . Assim, seguir sua sugestão também tornaria o trabalho com indicadores mais difícil. (Embora, pessoalmente, eu prefira explicitamente comparar ponteiros com NULL , em vez de tratá-los como booleanos).

    
por 15.05.2013 / 23:36
fonte
4

Zero pode ser falso porque a maioria das CPUs tem um sinalizador ZERO que pode ser usado para ramificar. Ele salva uma operação de comparação.

Vamos ver o porquê.

Algum psuedocode, já que o público provavelmente não lê o assembly

c- source chamadas de loop simples wibble 10 vezes

for (int foo =10; foo>0; foo-- ) /* down count loop is shorter */
{  
   wibble();
}

alguma montagem fingida para isso

0x1000 ld a 0x0a      'foo=10
0x1002 call 0x1234    'call wibble()
0x1005 dec a          'foo--
0x1006 jrnz -0x06      'jump back to 0x1000 if not zero
0x1008  

c- source outro loop simples chama wibble 10 vezes

for (int foo =0; foo<10; foo-- ) /* up count loop is longer  */
{  
   wibble();
}

alguma montagem fingida para este caso

0x1000 ld a 0x00      'foo=0
0x1002 call 0x1234    'call wibble()
0x1005 dec a          'foo--
0x1006 cmp 0x0a       'compare foo to 10 ( like a subtract but we throw the result away)
0x1008 jrns -0x08      'jump back to 0x1000 if compare was negative
0x100a  

mais algumas fontes c

int foo=10;
if ( foo ) wibble()

e a montagem

0x1000 ld a 0x10
0x1002 jz 0x3
0x1004 call 0x1234
0x1007  

veja como isso é curto?

mais algumas fontes c

int foo=10;
if ( foo==0 ) wibble()

e o assembly (vamos assumir um compilador ligeiramente inteligente que pode substituir == 0 sem comparação)

0x1000 ld a 0x10
0x1002 jz 0x3
0x1004 call 0x1234
0x1007  

Agora vamos tentar uma convenção de verdadeiro = 1

mais algumas fontes c     #define TRUE 1     int foo = TRUE;     if (foo == TRUE) wibble ()

e a montagem

0x1000 ld a 0x1
0x1002 cmp a 0x01
0x1004 jz 0x3
0x1006 call 0x1234
0x1009 

veja quão curto é o caso de true diferente de zero?

CPUs realmente precisas tinham pequenos conjuntos de sinalizadores ligados ao Acumulador.

Para verificar se a > b ou a = b geralmente leva uma instrução de comparação.

  • A menos que B seja ZERO - nesse caso, o sinalizador ZERO é definido Implementado como um simples NOR lógico ou todos os bits no acumulador.
  • Ou NEGATIVO, em que apenas use o "bit de sinal", ou seja, o bit mais significativo do Acumulador, se você estiver usando a aritmética de complemento de dois. (Principalmente nós)

Vamos reafirmar isso. Em algumas CPUs mais antigas, você não precisou usar uma instrução de comparação para o acumulador igual a ZERO, ou um acumulador menor que zero.

Agora você vê porque o zero pode ser falso?

Por favor, note que este é o código psuedo e nenhum conjunto de instruções real é bem parecido com isto. Se você conhece a montagem, sabe que estou simplificando muito as coisas aqui. Se você sabe alguma coisa sobre o design do compilador, não precisa ler esta resposta. Qualquer um que saiba alguma coisa sobre desenrolamento de loop ou predição de ramificação, a classe avançada fica no final do corredor na sala 203.

    
por 16.05.2013 / 11:34
fonte
3

Existem muitas respostas que sugerem que a correspondência entre 1 e verdadeiro é necessária por alguma propriedade matemática. Não consigo encontrar nenhuma propriedade desse tipo e sugiro que seja uma convenção puramente histórica.

Dado um campo com dois elementos, temos duas operações: adição e multiplicação. Podemos mapear operações booleanas nesse campo de duas maneiras:

Tradicionalmente, identificamos True com 1 e False com 0. Identificamos AND com * e XOR com +. Assim, OR está saturando a adição.

No entanto, poderíamos facilmente identificar True com 0 e False com 1. Então, identificamos OR com * e XNOR com +. Assim, AND está saturando a adição.

    
por 16.05.2013 / 00:01
fonte
3

Estranhamente, zero nem sempre é falso.

Em particular, a convenção Unix e Posix é definir EXIT_SUCCESS como 0 (e EXIT_FAILURE como 1). Na verdade, é até uma convenção-padrão C !

Assim, para os shells Posix e exit (2) syscalls, 0 significa "successful "que intuitivamente é mais verdadeiro que falso.

Em particular, o if do shell quer um retorno do processo EXIT_SUCCESS (ou seja 0) para seguir sua ramificação "then"!

No Esquema (mas não no Common Lisp ou no MELT ) 0 e nil (isto é, () no Esquema) são verdadeiros, já que o único valor falso é #f

Eu concordo, estou com um problema!

    
por 28.07.2014 / 15:07
fonte
3

C é usado para programação de baixo nível perto de hardware, uma área em que às vezes você precisa alternar entre operações lógicas e bit a bit, nos mesmos dados. Ser obrigado a converter uma expressão numérica em booleana apenas para realizar um teste, iria atrapalhar o código.

Você pode escrever coisas como:

if (modemctrl & MCTRL_CD) {
   /* carrier detect is on */
}

em vez de

if ((modemctrl & MCTRL_CD) != 0) {
    /* carrier detect is on */
}

Em um exemplo isolado, não é tão ruim, mas ter que fazer isso vai ficar cansativo.

Da mesma forma, converse operações. É útil para o resultado de uma operação booleana, como uma comparação, apenas produzir um 0 ou 1: Suponha que desejamos definir o terceiro bit de alguma palavra com base no fato de modemctrl ter o bit de detecção de portadora:

flags |= ((modemctrl & MCTRL_CD) != 0) << 2;

Aqui temos que ter o != 0 , para reduzir o resultado da expressão bi- & para 0 ou 1 , mas como o resultado é apenas um inteiro, somos poupados de ter que adicionar alguns elenco irritante para converter ainda mais booleano para inteiro.

Mesmo que o C moderno tenha agora um bool , ele ainda preserva a validade do código como este, porque é bom, e por causa da quebra massiva de compatibilidade reversa que seria causada de outra forma.

Outro exemplo em que C é bom: testar duas condições booleanas como um interruptor de quatro direções:

switch (foo << 1 | bar) {  /* foo and bar booleans are 0 or 1 */
case 0: /* !foo && !bar */
   break;
case 1: /* !foo && bar */
   break;
case 2: /* foo && !bar */
   break;
case 3: /* foo && bar */
   break;
}

Você não pode tirar isso do programador C sem lutar!

Por último, C às vezes serve como um tipo de linguagem assembly de alto nível. Nas linguagens assembly, também não temos tipos booleanos. Um valor booleano é apenas um bit ou um valor zero versus valor diferente de zero em um local ou registrador de memória. Um zero inteiro, zero booleano e o endereço zero são todos testados da mesma maneira em conjuntos de instruções em linguagem assembly (e talvez até em ponto flutuante zero). A semelhança entre C e linguagem assembly é útil, por exemplo, quando C é usado como idioma de destino para compilar outro idioma (até mesmo um que tenha um tipo de letra booleano)!

    
por 16.05.2013 / 03:16
fonte
0

Um valor booleano ou verdadeiro tem apenas 2 valores. Verdadeiro e falso.

Estes devem não ser representados como inteiros, mas como bits (0 e 1).

Dizer qualquer outro inteiro ao lado de 0 ou 1 não é falso é uma declaração confusa. As tabelas verdadeiras lidam com valores verdadeiros, não inteiros.

De um valor de verdade prospectivo, -1 ou 2 quebraria todas as tabelas de verdade e qualquer lógica booleana associada a elas.

  • 0 E -1 ==?!
  • 0 OU 2 ==?!

A maioria das linguagens geralmente tem um tipo boolean que, quando convertido para um tipo de número, como inteiro, revela falso para ser convertido como um valor inteiro de 0.

    
por 15.05.2013 / 23:17
fonte
-6

Por fim, você está falando sobre quebrar a linguagem principal porque algumas APIs são ruins. As APIs de baixa qualidade não são novas e você não pode corrigi-las quebrando o idioma. É um fato matemático que 0 é falso e 1 é verdadeiro, e qualquer idioma que não respeita isso é fundamentalmente quebrado. A comparação de três vias é um nicho e não tem nenhum negócio, pois o resultado é converter implicitamente para bool , uma vez que retorna três resultados possíveis. As antigas APIs C simplesmente têm um tratamento de erros terrível e também são limitadas porque o C não possui os recursos de linguagem necessários para não ter interfaces terríveis.

Note que não estou dizendo isso para idiomas que não têm conversão booleana inteira & gt implícita.

    
por 15.05.2013 / 21:52
fonte