Explique este fragmento que encuentra el máximo de dos enteros sin usar if-else o cualquier otro operador de comparación.


78

Calcula el máximo de dos números. No debe utilizar if-else ni ningún otro operador de comparación. Encontré esta pregunta en el tablón de anuncios en línea, así que pensé que debería preguntar en StackOverflow

EJEMPLO Entrada: 5, 10 Salida: 10

Encontré esta solución, ¿alguien puede ayudarme a comprender estas líneas de código?


1
Consideraría hacer trampa en el bit de señal, ya que es básicamente lo mismo que hace el procesador <.
starblue

7
Para C ++ al menos, desde 5.8.3, discutiendo E1 >> E2: "Si E1 tiene un tipo con signo y un valor negativo, el valor resultante está definido por la implementación", por lo que "c >> 31" puede cambiar o no un signo de la parte más a la menos significativa ....
Tony Delroy

1
y no hay forma de decir que el bit 31 es el bit de signo de todos modos.
Antti Haapala

3
De todos modos, esta pregunta debería cerrarse porque nadie lee el texto de la pregunta, ni la etiqueta que es C.
Antti Haapala

Solo porque nadie más lo ha notado. Este es el famoso truco de juguete que se encuentra aquí , disfrazado.
kaartic

Respuestas:


121

Analicemos esto. Esta primera línea parece sencilla: almacena la diferencia de ay b. Este valor es negativo si a < by no es negativo en caso contrario. En realidad hay un error aquí - si la diferencia de los números ay bes tan grande que no puede caber en un entero, esto dará lugar a un comportamiento indefinido - ¡Uy! Así que supongamos que eso no sucede aquí.

En la siguiente línea, que es

la idea es comprobar si el valor de ces negativo. En prácticamente todas las computadoras modernas, los números se almacenan en un formato llamado complemento a dos en el que el bit más alto del número es 0 si el número es positivo y 1 si el número es negativo. Además, la mayoría de las entradas son de 32 bits. (c >> 31)desplaza el número 31 bits hacia abajo, dejando el bit más alto del número en el lugar del bit más bajo. El siguiente paso de tomar este número y anotarlo con 1 (cuya representación binaria es 0 en todas partes excepto el último bit) borra todos los bits más altos y solo le da el bit más bajo. Dado que el bit más bajo de c >> 31es el bit más alto de c, esto lee el bit más alto de ccomo 0 o 1. Dado que el bit más alto es 1 sif ces 1, esta es una forma de verificar sices negativo (1) o positivo (0). Combinando este razonamiento con el anterior, kes 1 si a < by es 0 en caso contrario.

El paso final es hacer esto:

Si a < b, entonces k == 1y k * c = c = a - b, y así

Cuál es el máximo correcto, ya que a < b. De lo contrario, si a >= b, entonces k == 0y

Que es también el máximo correcto.


7
Todo esto asumiendo que no hay desbordes.
Peter Taylor

4
@templatetypedef, suponga que a = 0x80000000 (el valor mínimo de un int) y b = 1. ¿Qué es c?
Peter Taylor

4
@ Peter Taylor- Ese es un buen punto. Tenga en cuenta que no se me ocurrió esta respuesta; Solo estaba explicando el código del OP. :-) Pero tienes razón en que esto se romperá si los números están demasiado separados.
templatetypedef

1
@templatetypedef, lo sé: estaba a mitad de camino escribiendo una respuesta muy similar cuando publicaste la tuya. Solo lo estaba señalando en beneficio de OP.
Peter Taylor

3
@Ani, el bit superior se desplaza a cada posición por la que pasa. La alternativa seríamax = a + (c >> 31) * c
Peter Taylor

28

Aquí vamos: (a + b) / 2 + |a - b| / 2


1
¿Conoce la lógica matemática detrás de esto?
Senthil Kumaran

6
@ mike.did: ¿Puedes hacer | a - b | sin condicionales?
templatetypedef

8
Es un poco exagerado suponer que el valor absoluto es un operador que se puede utilizar para este problema. Es una buena respuesta, matemáticamente, pero dudo que sea aceptada.
Keith Irwin

5
-1 Incorrecto en ints, por ejemplo (3 + 2) / 2 + |3 - 2| / 2 = 2 + 0 = 2 != 3.
starblue

4
@starblue: ((3 + 2) + | 3-2 |) / 2 = 3 Parece perfecto desde aquí.
Michael Foukarakis

20

Utilice trucos bit a bit

Si sabe eso INT_MIN <= x - y <= INT_MAX,, puede usar lo siguiente, que es más rápido porque (x - y)solo necesita ser evaluado una vez.

Fuente: Bit Twiddling Hacks por Sean Eron Anderson


14
tenga en cuenta que la primera propuesta rompe la restricción de "operador sin comparación".
Matthieu M.

12

Esto se basa en la misma técnica que la solución de mike.dld , pero es menos "obvio" aquí lo que estoy haciendo. Una operación "abs" parece que está comparando el signo de algo, pero aquí estoy aprovechando el hecho de que sqrt () siempre le devolverá la raíz cuadrada positiva, por lo que estoy elevando al cuadrado (ab) escribiéndolo en su totalidad y luego en cuadrado. enraizándolo nuevamente, agregando a + by dividiendo por 2.

Verá que siempre funciona: por ejemplo, el ejemplo del usuario de 10 y 5 obtiene sqrt (100 + 25 - 100) = 5, luego suma 10 y 5 le da 20 y dividir por 2 le da 10.

Si usamos 9 y 11 como números, obtendríamos (sqrt (121 + 81-198) + 11 + 9) / 2 = (sqrt (4) + 20) / 2 = 22/2 = 11


a * a se desbordará fácilmente
Dvole

8

La respuesta más simple está a continuación.


6

Esta solución evita la multiplicación. m será 0x00000000 o 0xffffffff


4

Usando la idea cambiante para extraer el letrero publicado por otros, aquí hay otra forma:

Esto empuja los dos números a una matriz con el número máximo dado por el elemento de matriz cuyo índice es el bit de signo de la diferencia entre los dos números.

Tenga en cuenta que:

  1. La diferencia (a - b) puede desbordarse.
  2. Si los números no están firmados y el >>operador se refiere a un desplazamiento lógico a la derecha, no & 1es necesario.

4

Así es como creo que haría el trabajo. No es tan legible como te gustaría, pero cuando comienzas con "cómo hago X sin usar la forma obvia de hacer X, tienes que esperar eso. En teoría, esto también renuncia a cierta portabilidad, pero tú" Tendría que encontrar un sistema bastante inusual para ver un problema.

Esto tiene algunas ventajas sobre el que se muestra en la pregunta. En primer lugar, calcula el tamaño correcto de desplazamiento, en lugar de estar codificado para entradas de 32 bits. En segundo lugar, con la mayoría de los compiladores podemos esperar que toda la multiplicación ocurra en el momento de la compilación, por lo que todo lo que queda en el tiempo de ejecución es una manipulación trivial de bits (restar y cambiar) seguida de una carga y un retorno. En resumen, es casi seguro que esto sea bastante rápido, incluso en el microcontrolador más pequeño, donde el original usaba una multiplicación que tenía que ocurrir en tiempo de ejecución, por lo que aunque probablemente sea bastante rápido en una máquina de escritorio, a menudo será bastante un poco más lento en un pequeño microcontrolador.


2

Esto es lo que hacen esas líneas:

c es ab. si c es negativo, a <b.

k es el bit 32 de c, que es el bit de signo de c (asumiendo enteros de 32 bits. Si se hace en una plataforma con enteros de 64 bits, este código no funcionará). Se ha desplazado 31 bits hacia la derecha para eliminar los 31 bits más a la derecha, dejando el bit de signo en el lugar más a la derecha y luego anularlo con 1 para eliminar todos los bits a la izquierda (que se llenarán con 1 si c es negativo). Entonces k será 1 si c es negativo y 0 si c es positivo.

Entonces max = a - k * c. Si c es 0, esto significa a> = b, entonces max es a - 0 * c = a. Si c es 1, esto significa que a <b y luego a - 1 * c = a - (a - b) = a - a + b = b.

En general, solo se usa el bit de signo de la diferencia para evitar usar operaciones mayor o menor que. Honestamente, es un poco tonto decir que este código no usa una comparación. c es el resultado de comparar ay b. El código simplemente no usa un operador de comparación. Podría hacer algo similar en muchos códigos de ensamblaje simplemente restando los números y luego saltando según los valores establecidos en el registro de estado.

También debo agregar que todas estas soluciones suponen que los dos números son enteros. Si son flotantes, dobles o algo más complicado (BigInts, números racionales, etc.) entonces realmente tienes que usar un operador de comparación. Los trucos de bits generalmente no sirven para esos.


1

Función getMax () sin ninguna operación lógica


Explicación:

Vamos a romper el 'máximo' en pedazos,

Entonces la función debería verse así:

Ahora,

En un número entero positivo, el primer bit (bit de signo) es 0 ; en negativo es- 1 . Desplazando bits a la derecha (>>) se puede capturar el primer bit.

Durante el desplazamiento a la derecha, el espacio vacío se llena con el bit de signo. Entonces 01110001 >> 2 = 00011100 , mientras que 10110001 >> 2 = 11101100 .

Como resultado, para el cambio de número de 8 bits, 7 bits producirán- 1 1 1 1 1 1 1 [0 o 1] para negativo, o 0 0 0 0 0 0 0 [0 o 1] para positivo.

Ahora, si la operación OR se realiza con 00000001 (= 1) , el número negativo produce- 11111111 (= -1) y positivo- 00000001 (= 1) .

Entonces,

Finalmente,


Otra forma -


0

estático int mymax (int a, int b)

Si b> a entonces (ab) será negativo, el signo devolverá -1, al sumar 1 obtenemos el índice 0 que es b, si b = a entonces ab será 0, +1 dará 1 índice por lo que no importa si devolvemos aob, cuando a> b entonces ab será positivo y el signo devolverá 1, sumando 1 obtenemos el índice 2 donde se almacena a.


0

0

El código que proporciono es para encontrar el máximo entre dos números, los números pueden ser de cualquier tipo de datos (entero, flotante). Si los números de entrada son iguales, la función devuelve el número.

Descripción

  • La primera cosa que la función toma los argumentos es doble y tiene el tipo de retorno como doble. La razón de esto es que para crear una única función que pueda encontrar el máximo para todos los tipos. Cuando se proporcionan números de tipo entero o uno es un número entero y el otro es el punto flotante, también debido a la conversión implícita, la función también se puede usar para encontrar el máximo para los números enteros.
  • La lógica básica es simple, digamos que tenemos dos números a & b si ab> 0 (es decir, la diferencia es positiva), entonces a es máximo, de lo contrario si ab == 0 entonces ambos son iguales y si ab <0 (es decir, la diferencia es - ve) b es máximo.
  • El bit de signo se guarda como el bit más significativo (MSB) en la memoria. Si MSB es 1 y viceversa. Para comprobar si MSB es 1 o 0, cambiamos el MSB a la posición LSB y Bitwise & con 1, si el resultado es 1, entonces el número es -ve en caso contrario no. es + ve. Este resultado se obtiene mediante el enunciado:

    int_diff >> (tamaño de (int) * 8 - 1) & 1

Aquí, para obtener el bit de signo del MSB al LSB, lo cambiamos a la derecha a k-1 bits (donde k es el número de bits necesarios para guardar un número entero en la memoria que depende del tipo de sistema). Aquí k = sizeof (int) * 8 ya que sizeof () da el número de bytes necesarios para guardar un entero para obtener no. de bits, lo multiplicamos por 8. Después del desplazamiento a la derecha, aplicamos el bit a bit & con 1 para obtener el resultado.

  • Ahora, después de obtener el resultado (supongamos que es r) como 1 (para -ve diff) y 0 (para + ve diff) multiplicamos el resultado con la diferencia de los dos números, la lógica se da de la siguiente manera:

    1. si a> b entonces ab> 0 es decir, es + ve entonces el resultado es 0 (es decir, r = 0). Entonces a- (ab) * r => a- (ab) * 0, lo que da 'a' como máximo.
    2. si a <b entonces ab <0 es decir, es -ve entonces el resultado es 1 (es decir, r = 1). Entonces a- (ab) * r => a- (ab) * 1 => a-a + b => b, lo que da 'b' como máximo.
  • Ahora quedan dos puntos restantes: 1. el uso del bucle while y 2. por qué he usado la variable 'int_diff' como un número entero. Para responder a estas correctamente debemos entender algunos puntos:

    1. Los valores de tipo flotante no se pueden utilizar como operando para los operadores bit a bit.
    2. Debido a la razón anterior, necesitamos obtener el valor en un valor entero para obtener el signo de diferencia mediante el uso de operadores bit a bit. Estos dos puntos describen la necesidad de la variable 'int_diff' como tipo entero.
    3. Ahora digamos que encontramos la diferencia en la variable 'diff', ahora hay 3 posibilidades para los valores de 'diff' independientemente del signo de estos valores. (un). | diff |> = 1, (b). 0 <| diff | <1, (c). | diff | == 0.
    4. Cuando asignamos un valor doble a una variable entera, la parte decimal se pierde.
    5. Para el caso (a) el valor de 'int_diff'> 0 (es decir, 1,2, ...). Para otros dos casos int_diff = 0.
    6. La condición (temp_diff-int_diff) || 0.0 comprueba si diff == 0 para que ambos números sean iguales.
    7. Si diff! = 0, entonces verificamos si int_diff | 0 es verdadero, es decir, el caso (b) es verdadero
    8. En el ciclo while, intentamos obtener el valor de int_diff como distinto de cero para que el valor de int_diff también obtenga el signo de diff.

0

Aquí hay un par de métodos de alteración de bits para obtener el máximo de dos valores integrales:

Método 1

Explicación:

  • (a - b) >> SIGN_BIT_SHIFT - Si a > bentonces a - bes positivo, entonces el bit de signo es 0y la máscara es 0x00.00. De a < blo contrario, también a - bes negativo, el bit de signo es 1y después de cambiar, obtenemos una máscara de0xFF..FF
  • (a & ~ mask): si la máscara es 0xFF..FF, entonces ~maskes 0x00..00y entonces este valor es 0. De lo contrario, ~maskes 0xFF..FFy el valor esa
  • (b & máscara): si la máscara es 0xFF..FF, este valor es b. De lo contrario, maskes 0x00..00y el valor es 0.

Finalmente:

  • Si a >= bentonces a - bes positivo, obtenemosmax = a | 0 = a
  • Si a < bentonces a - bes negativo, obtenemosmax = 0 | b = b

Método 2

Explicación:

  • La explicación de la máscara es la misma que para el método 1 . Si a > bla máscara es 0x00..00, de lo contrario, la máscara0xFF..FF
  • Si la máscara es 0x00..00, entonces (a ^ b) & maskes0x00..00
  • Si la máscara es 0xFF..FF, entonces (a ^ b) & maskesa ^ b

Finalmente:

  • Si a >= bobtenemosa ^ 0x00..00 = a
  • Si a < bobtenemosa ^ a ^ b = b

0

// En C # puede usar la biblioteca matemática para realizar la función mínima o máxima

usando el sistema;

class NumberComparator {

}


Tenga cuidado: la pregunta OP está etiquetada [c] no [c #]
Alex Yu

0

Sin operadores lógicos, sin bibliotecas (JS)


-2

La lógica descrita en un problema se puede explicar como si el primer número es menor, entonces se restará 0, de lo contrario se restará la diferencia del primer número para obtener el segundo número. Encontré una solución matemática más que creo que es un poco más simple de entender este concepto.

Considerando a y b como números dados

Nuevamente, la idea es encontrar k que sea 0 o 1 y multiplicarlo con la diferencia de dos números. Y finalmente este número debe restarse del primer número para obtener el menor de los dos números. PD: esta solución fallará en caso de que el segundo número sea cero


-3

Hay una forma

y uno


-3

-4

Supongo que podemos simplemente multiplicar los números con sus comparaciones bit a bit, por ejemplo:

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.