start + (end - start) / 2também carrega significado semântico: (end - start)é o comprimento, de modo que este diz: start + half the length.
Njzk2 01/08/19
2
@ LưuVĩnhPhúc: Esta pergunta não tem as melhores respostas e mais votos? Nesse caso, as outras perguntas provavelmente devem ser encerradas como uma brincadeira dessa. A idade das postagens é irrelevante.
Nisse Engström
Respostas:
218
Existem três razões.
Primeiro de tudo, start + (end - start) / 2funciona mesmo se você estiver usando ponteiros, desde end - startque não exceda 1 .
int*start =...,*end =...;int*mid = start +(end - start)/2;// works as expectedint*mid =(start + end)/2;// type error, won't compile
Segundo, start + (end - start) / 2não transbordará se starte endfor um grande número positivo. Com operandos assinados, o estouro é indefinido:
int start =0x7ffffffe, end =0x7fffffff;int mid = start +(end - start)/2;// works as expectedint mid =(start + end)/2;// overflow... undefined
(Observe que end - startpode estourar, mas apenas se start < 0ou end < 0.)
Ou com aritmética não assinada, o excesso é definido, mas fornece a resposta errada. No entanto, para operandos não assinados, start + (end - start) / 2nunca excederá o tempo end >= start.
unsigned start =0xfffffffeu, end =0xffffffffu;unsigned mid = start +(end - start)/2;// works as expectedunsigned mid =(start + end)/2;// mid = 0x7ffffffe
Por fim, muitas vezes você deseja arredondar para o startelemento.
int start =-3, end =0;int mid = start +(end - start)/2;// -2, closer to startint mid =(start + end)/2;// -1, surprise!
Notas de rodapé
1 De acordo com o padrão C, se o resultado da subtração do ponteiro não for representável como a ptrdiff_t, o comportamento será indefinido. No entanto, na prática, isso requer a alocação de uma charmatriz usando pelo menos metade do espaço de endereço inteiro.
O resultado de (end - start)no signed intcaso é indefinido quando transborda.
ensc 31/07
Você pode provar que end-startnão vai transbordar? AFAIK, se você tomar um negativo start, deve ser possível transbordar. Claro, na maioria das vezes quando você calcular a média você sabe que os valores são >= 0...
Bakuriu
12
@Bakuriu: É impossível provar algo que não é verdade.
Dietrich Epp
4
É de particular interesse em C, pois a subtração do ponteiro (de acordo com o padrão) é interrompida pelo design. É permitido às implementações criar matrizes tão grandes que não end - startsão definidas, porque os tamanhos dos objetos não são assinados e as diferenças de ponteiro são assinadas. Portanto, end - start"funciona mesmo usando ponteiros", desde que você também mantenha o tamanho da matriz abaixo PTRDIFF_MAX. Para ser justo com o padrão, isso não é um obstáculo para a maioria das arquiteturas, já que é metade do tamanho do mapa de memória.
Steve Jessop
3
@Bakuriu: A propósito, existe um botão "editar" na postagem que você pode usar para sugerir alterações (ou fazer você mesmo) se você acha que perdi alguma coisa ou algo não está claro. Eu sou apenas humano, e este post foi visto por mais de dois mil pares de olhos. O tipo de comentário "Você deveria esclarecer ..." realmente me irrita.
Dietrich Epp
18
Podemos usar um exemplo simples para demonstrar esse fato. Suponha que em uma certa matriz grande , estamos tentando encontrar o ponto médio do intervalo [1000, INT_MAX]. Agora, INT_MAXé o maior valor que o inttipo de dados pode armazenar. Mesmo se 1for adicionado a isso, o valor final se tornará negativo.
Além disso, start = 1000e end = INT_MAX.
Usando a fórmula: (start + end)/2,
o ponto médio será
(1000 + INT_MAX)/2= -(INT_MAX+999)/2, que é negativo e pode causar falha de segmentação se tentarmos indexar usando esse valor.
Mas, usando a fórmula, (start + (end-start)/2)obtemos:
Se você adicionar 1 a INT_MAX, o resultado não será negativo, mas indefinido.
Celtschk
@celtschk Teoricamente, sim. Praticamente ele vai embrulhar-em torno de um monte de vezes que vão desde INT_MAXa -INT_MAX. É um mau hábito confiar nisso.
Mast
17
Para acrescentar ao que outros já disseram, o primeiro explica seu significado mais claramente aos menos atentos matematicamente:
mid = start +(end - start)/2
lê como:
início igual a meio mais metade do comprimento.
enquanto que:
mid =(start + end)/2
lê como:
meio é igual à metade do início e do fim
O que não parece tão claro quanto o primeiro, pelo menos quando expresso assim.
como Kos apontou, também pode ler:
meio é igual à média do início e do fim
O que é mais claro, mas ainda não é, pelo menos na minha opinião, tão claro quanto o primeiro.
Entendo o seu ponto, mas isso realmente é um exagero. Se você vê "e - s" e pensa em "comprimento", quase certamente vê "(s + e) / 2" e pensa em "média" ou "médio".
precisa saber é o seguinte
2
@djechlin Os programadores são pobres em matemática. Eles estão ocupados fazendo seu trabalho. Eles não têm tempo para assistir às aulas de matemática.
Little Alien
1
start + (end-start) / 2 pode evitar um possível estouro, por exemplo start = 2 ^ 20 e end = 2 ^ 30
(start + end)
pode estourar, enquanto(end - start)
não pode.start
eend
é ponteiro.start + (end - start) / 2
também carrega significado semântico:(end - start)
é o comprimento, de modo que este diz:start + half the length
.Respostas:
Existem três razões.
Primeiro de tudo,
start + (end - start) / 2
funciona mesmo se você estiver usando ponteiros, desdeend - start
que não exceda 1 .Segundo,
start + (end - start) / 2
não transbordará sestart
eend
for um grande número positivo. Com operandos assinados, o estouro é indefinido:(Observe que
end - start
pode estourar, mas apenas sestart < 0
ouend < 0
.)Ou com aritmética não assinada, o excesso é definido, mas fornece a resposta errada. No entanto, para operandos não assinados,
start + (end - start) / 2
nunca excederá o tempoend >= start
.Por fim, muitas vezes você deseja arredondar para o
start
elemento.Notas de rodapé
1 De acordo com o padrão C, se o resultado da subtração do ponteiro não for representável como a
ptrdiff_t
, o comportamento será indefinido. No entanto, na prática, isso requer a alocação de umachar
matriz usando pelo menos metade do espaço de endereço inteiro.fonte
(end - start)
nosigned int
caso é indefinido quando transborda.end-start
não vai transbordar? AFAIK, se você tomar um negativostart
, deve ser possível transbordar. Claro, na maioria das vezes quando você calcular a média você sabe que os valores são>= 0
...end - start
são definidas, porque os tamanhos dos objetos não são assinados e as diferenças de ponteiro são assinadas. Portanto,end - start
"funciona mesmo usando ponteiros", desde que você também mantenha o tamanho da matriz abaixoPTRDIFF_MAX
. Para ser justo com o padrão, isso não é um obstáculo para a maioria das arquiteturas, já que é metade do tamanho do mapa de memória.Podemos usar um exemplo simples para demonstrar esse fato. Suponha que em uma certa matriz grande , estamos tentando encontrar o ponto médio do intervalo
[1000, INT_MAX]
. Agora,INT_MAX
é o maior valor que oint
tipo de dados pode armazenar. Mesmo se1
for adicionado a isso, o valor final se tornará negativo.Além disso,
start = 1000
eend = INT_MAX
.Usando a fórmula:
(start + end)/2
,o ponto médio será
Mas, usando a fórmula,
(start + (end-start)/2)
obtemos:fonte
INT_MAX
, o resultado não será negativo, mas indefinido.INT_MAX
a-INT_MAX
. É um mau hábito confiar nisso.Para acrescentar ao que outros já disseram, o primeiro explica seu significado mais claramente aos menos atentos matematicamente:
lê como:
enquanto que:
lê como:
O que não parece tão claro quanto o primeiro, pelo menos quando expresso assim.
como Kos apontou, também pode ler:
O que é mais claro, mas ainda não é, pelo menos na minha opinião, tão claro quanto o primeiro.
fonte
start + (end-start) / 2 pode evitar um possível estouro, por exemplo start = 2 ^ 20 e end = 2 ^ 30
fonte