start + (end - start) / 2También tiene un significado semántico: (end - start)es la longitud, por lo que este dice: start + half the length.
njzk2
2
@ LưuVĩnhPhúc: ¿Esta pregunta no tiene las mejores respuestas y la mayoría de los votos? Si es así, las otras preguntas probablemente deberían cerrarse como un duplicado de esta. La edad de las publicaciones es irrelevante.
Nisse Engström
Respuestas:
218
Hay tres razones
En primer lugar, start + (end - start) / 2funciona incluso si está utilizando punteros, siempre que end - startno se desborde 1 .
int*start =...,*end =...;int*mid = start +(end - start)/2;// works as expectedint*mid =(start + end)/2;// type error, won't compile
En segundo lugar, start + (end - start) / 2¿No desbordamiento si starty endson grandes números positivos. Con operandos firmados, el desbordamiento no está definido:
int start =0x7ffffffe, end =0x7fffffff;int mid = start +(end - start)/2;// works as expectedint mid =(start + end)/2;// overflow... undefined
(Tenga en cuenta que end - startpuede desbordarse, pero solo si start < 0o end < 0.)
O con aritmética sin signo, se define el desbordamiento pero le da la respuesta incorrecta. Sin embargo, para operandos sin firmar, start + (end - start) / 2nunca se desbordará mientras end >= start.
unsigned start =0xfffffffeu, end =0xffffffffu;unsigned mid = start +(end - start)/2;// works as expectedunsigned mid =(start + end)/2;// mid = 0x7ffffffe
Finalmente, a menudo quieres redondear hacia el startelemento.
int start =-3, end =0;int mid = start +(end - start)/2;// -2, closer to startint mid =(start + end)/2;// -1, surprise!
Notas al pie
1 Según el estándar C, si el resultado de la resta del puntero no es representable como a ptrdiff_t, entonces el comportamiento es indefinido. Sin embargo, en la práctica, esto requiere asignar una charmatriz utilizando al menos la mitad del espacio de direcciones completo.
El resultado de (end - start)en el signed intcaso es indefinido cuando se desborda.
ensc
¿Puedes demostrar que end-startno se desbordará? AFAIK si toma un negativo start, debería ser posible hacer que se desborde. Claro, la mayoría de las veces cuando calculas el promedio sabes que los valores son >= 0...
Bakuriu
12
@Bakuriu: Es imposible probar algo que no es cierto.
Dietrich Epp
44
Es de particular interés en C, ya que la resta del puntero (según el estándar) se rompe por diseño. Se permite que las implementaciones creen matrices tan grandes que end - startno estén definidas, porque los tamaños de los objetos no están firmados, mientras que las diferencias de puntero están firmadas. Entonces, end - start"funciona incluso utilizando punteros", siempre que de alguna manera también mantenga el tamaño de la matriz a continuación PTRDIFF_MAX. Para ser justos con el estándar, eso no es una gran obstrucción en la mayoría de las arquitecturas, ya que es la mitad del tamaño del mapa de memoria.
Steve Jessop
3
@Bakuriu: Por cierto, hay un botón "editar" en la publicación que puedes usar para sugerir cambios (o hacerlos tú mismo) si crees que me he perdido algo o algo no está claro. Solo soy humano, y esta publicación ha sido vista por más de dos mil pares de globos oculares. El tipo de comentario, "Deberías aclarar ..." realmente me molesta.
Dietrich Epp
18
Podemos tomar un ejemplo simple para demostrar este hecho. Supongamos que en una determinada matriz grande , estamos tratando de encontrar el punto medio del rango [1000, INT_MAX]. Ahora, INT_MAXes el valor más grande que intpuede almacenar el tipo de datos. Incluso si 1se agrega a esto, el valor final será negativo.
Además, start = 1000y end = INT_MAX.
Utilizando la fórmula: (start + end)/2,
el punto medio será
(1000 + INT_MAX)/2= -(INT_MAX+999)/2, que es negativo y puede dar un error de segmentación si intentamos indexar utilizando este valor.
Pero, usando la fórmula (start + (end-start)/2), obtenemos:
(1000 + (INT_MAX-1000)/2)= (1000 + INT_MAX/2 - 500)= (INT_MAX/2 + 500)que no se desbordará .
Entiendo tu punto, pero esto realmente es un tramo. Si ve "e - s" y piensa "longitud", entonces seguramente verá "(s + e) / 2" y piensa "promedio" o "medio".
djechlin
2
@djechlin Los programadores son pobres en matemáticas. Están ocupados haciendo su trabajo. No tienen tiempo para asistir a las clases de matemáticas.
Little Alien
1
start + (end-start) / 2 puede evitar un posible desbordamiento, por ejemplo start = 2 ^ 20 y end = 2 ^ 30
(start + end)
puede desbordarse, mientras(end - start)
que no puede.start
yend
son puntero.start + (end - start) / 2
También tiene un significado semántico:(end - start)
es la longitud, por lo que este dice:start + half the length
.Respuestas:
Hay tres razones
En primer lugar,
start + (end - start) / 2
funciona incluso si está utilizando punteros, siempre queend - start
no se desborde 1 .En segundo lugar,
start + (end - start) / 2
¿No desbordamiento sistart
yend
son grandes números positivos. Con operandos firmados, el desbordamiento no está definido:(Tenga en cuenta que
end - start
puede desbordarse, pero solo sistart < 0
oend < 0
.)O con aritmética sin signo, se define el desbordamiento pero le da la respuesta incorrecta. Sin embargo, para operandos sin firmar,
start + (end - start) / 2
nunca se desbordará mientrasend >= start
.Finalmente, a menudo quieres redondear hacia el
start
elemento.Notas al pie
1 Según el estándar C, si el resultado de la resta del puntero no es representable como a
ptrdiff_t
, entonces el comportamiento es indefinido. Sin embargo, en la práctica, esto requiere asignar unachar
matriz utilizando al menos la mitad del espacio de direcciones completo.fuente
(end - start)
en elsigned int
caso es indefinido cuando se desborda.end-start
no se desbordará? AFAIK si toma un negativostart
, debería ser posible hacer que se desborde. Claro, la mayoría de las veces cuando calculas el promedio sabes que los valores son>= 0
...end - start
no estén definidas, porque los tamaños de los objetos no están firmados, mientras que las diferencias de puntero están firmadas. Entonces,end - start
"funciona incluso utilizando punteros", siempre que de alguna manera también mantenga el tamaño de la matriz a continuaciónPTRDIFF_MAX
. Para ser justos con el estándar, eso no es una gran obstrucción en la mayoría de las arquitecturas, ya que es la mitad del tamaño del mapa de memoria.Podemos tomar un ejemplo simple para demostrar este hecho. Supongamos que en una determinada matriz grande , estamos tratando de encontrar el punto medio del rango
[1000, INT_MAX]
. Ahora,INT_MAX
es el valor más grande queint
puede almacenar el tipo de datos. Incluso si1
se agrega a esto, el valor final será negativo.Además,
start = 1000
yend = INT_MAX
.Utilizando la fórmula:
(start + end)/2
,el punto medio será
Pero, usando la fórmula
(start + (end-start)/2)
, obtenemos:fuente
INT_MAX
, el resultado no será negativo, sino indefinido.INT_MAX
a-INT_MAX
. Sin embargo, es un mal hábito confiar en eso.Para agregar a lo que otros ya han dicho, el primero explica su significado más claramente para aquellos con una mentalidad menos matemática:
se lee como:
mientras:
se lee como:
Lo que no parece tan claro como el primero, al menos cuando se expresa así.
como señaló Kos, también puede leer:
Lo cual es más claro pero aún no, al menos en mi opinión, tan claro como el primero.
fuente
start + (end-start) / 2 puede evitar un posible desbordamiento, por ejemplo start = 2 ^ 20 y end = 2 ^ 30
fuente