Techo rápido de una división entera en C / C ++

262

Dados los valores enteros xy y, C y C ++, ambos devuelven como cociente q = x/yel piso del equivalente de coma flotante. Estoy interesado en un método para devolver el techo en su lugar. Por ejemplo, ceil(10/5)=2y ceil(11/5)=3.

El enfoque obvio implica algo como:

q = x / y;
if (q * y < x) ++q;

Esto requiere una comparación y multiplicación extra; y otros métodos que he visto (de hecho) involucran la conversión como floato double. ¿Existe un método más directo que evite la multiplicación adicional (o una segunda división) y la ramificación, y que también evite la conversión como un número de coma flotante?

y y
fuente
70
la instrucción de división a menudo devuelve tanto el cociente como el resto al mismo tiempo, por lo que no hay necesidad de multiplicar, solo q = x/y + (x % y != 0);es suficiente
phuclv
2
@ LưuVĩnhPhúc ese comentario debería ser la respuesta aceptada, en mi opinión.
Andreas Grapentin
1
@ LưuVĩnhPhúc En serio, debes agregar eso como respuesta. Acabo de usar eso para mi respuesta durante una prueba de codilidad. Funcionó de maravilla, aunque no estoy seguro de cómo funciona la parte mod de la respuesta, pero funcionó.
Zachary Kraus
2
@AndreasGrapentin la respuesta a continuación de Miguel Figueiredo se presentó casi un año antes de que Lưu Vĩnh Phúc dejara el comentario anterior. Si bien entiendo cuán atractiva y elegante es la solución de Miguel, no estoy dispuesto a cambiar la respuesta aceptada en esta fecha tardía. Ambos enfoques siguen siendo sólidos. Si se siente lo suficientemente fuerte al respecto, le sugiero que muestre su apoyo votando la respuesta de Miguel a continuación.
andand
1
Extraño, no he visto ninguna medición o análisis sensato de las soluciones propuestas. Se habla de velocidad cerca del hueso, pero no se habla de arquitecturas, tuberías, instrucciones de ramificación y ciclos de reloj.
Rado

Respuestas:

394

Para números positivos

unsigned int x, y, q;

Para resumir ...

q = (x + y - 1) / y;

o (evitando el desbordamiento en x + y)

q = 1 + ((x - 1) / y); // if x != 0
Chispeante
fuente
66
@bitc: para números negativos, creo que C99 especifica redondeo a cero, también lo x/yes el techo de la división. C90 no especificó cómo redondear, y tampoco creo que el estándar actual de C ++ lo haga.
David Thornley
66
Ver la publicación de Eric Lippert: stackoverflow.com/questions/921180/c-round-up/926806#926806
Mashmagar
3
Nota: Esto puede desbordarse. q = ((largo largo) x + y - 1) / y no lo hará. Sin embargo, mi código es más lento, así que si sabes que tus números no se desbordarán, deberías usar la versión de Sparky.
Jørgen Fogh
1
@bitc: Creo que el punto de David era que no usarías el cálculo anterior si el resultado es negativo, solo q = x / y;
usarías
12
El segundo tiene un problema donde x es 0. ceil (0 / y) = 0 pero devuelve 1.
Omry Yadan
78

Para números positivos:

    q = x/y + (x % y != 0);
Miguel Figueiredo
fuente
55
La instrucción de división de la arquitectura más común también incluye el resto en su resultado, por lo que esto realmente solo necesita una división y sería muy rápido
phuclv
58

La respuesta de Sparky es una forma estándar de resolver este problema, pero como también escribí en mi comentario, corre el riesgo de desbordamientos. Esto se puede resolver utilizando un tipo más amplio, pero ¿qué pasa si desea dividir long longs?

La respuesta de Nathan Ernst proporciona una solución, pero implica una llamada a la función, una declaración de variable y un condicional, lo que lo hace no más corto que el código OP y probablemente aún más lento, porque es más difícil de optimizar.

Mi solución es esta:

q = (x % y) ? x / y + 1 : x / y;

Será un poco más rápido que el código OP, porque el módulo y la división se realizan utilizando la misma instrucción en el procesador, porque el compilador puede ver que son equivalentes. Al menos gcc 4.4.1 realiza esta optimización con el indicador -O2 en x86.

En teoría, el compilador podría incluir la llamada de función en el código de Nathan Ernst y emitir lo mismo, pero gcc no hizo eso cuando lo probé. Esto podría deberse a que vincularía el código compilado a una única versión de la biblioteca estándar.

Como nota final, nada de esto importa en una máquina moderna, excepto si está en un circuito extremadamente cerrado y todos sus datos están en registros o en el caché L1. De lo contrario, todas estas soluciones serán igualmente rápidas, excepto posiblemente la de Nathan Ernst, que podría ser significativamente más lenta si la función tiene que ser obtenida de la memoria principal.

Jørgen Fogh
fuente
3
Había una manera más fácil de arreglar el desbordamiento, simplemente reducía a / a:q = (x > 0)? 1 + (x - 1)/y: (x / y);
Ben Voigt
-1: esta es una forma ineficiente, ya que intercambia un * barato por un% costoso; peor que el enfoque OP.
Yves Daoust
2
No, no lo hace. Como expliqué en la respuesta, el operador% es libre cuando ya realiza la división.
Jørgen Fogh
1
Entonces, ¿ q = x / y + (x % y > 0);es más fácil que la ? :expresión?
Han
Depende de lo que quieras decir con "más fácil". Puede o no ser más rápido, dependiendo de cómo lo traduzca el compilador. Mi suposición sería más lenta, pero tendría que medirlo para estar seguro.
Jørgen Fogh
18

Puede usar la divfunción en cstdlib para obtener el cociente y el resto en una sola llamada y luego manejar el techo por separado, como en el siguiente

#include <cstdlib>
#include <iostream>

int div_ceil(int numerator, int denominator)
{
        std::div_t res = std::div(numerator, denominator);
        return res.rem ? (res.quot + 1) : res.quot;
}

int main(int, const char**)
{
        std::cout << "10 / 5 = " << div_ceil(10, 5) << std::endl;
        std::cout << "11 / 5 = " << div_ceil(11, 5) << std::endl;

        return 0;
}
Nathan Ernst
fuente
12
Como un caso interesante de la doble explosión, también podrías return res.quot + !!res.rem;:)
Sam Harwell
¿Ldiv no siempre promueve los argumentos en largos largos? ¿Y eso no cuesta nada, subir o bajar?
einpoklum
12

¿Qué tal esto? (requiere y no negativo, así que no use esto en el raro caso de que y sea una variable sin garantía de no negatividad)

q = (x > 0)? 1 + (x - 1)/y: (x / y);

Reduje y/ya uno, eliminando el término x + y - 1y con él cualquier posibilidad de desbordamiento.

Evito x - 1envolviendo alrededor cuando xes un tipo sin signo y contiene cero.

Para firmado x, negativo y cero aún se combinan en un solo caso.

Probablemente no sea un gran beneficio en una CPU moderna de uso general, pero esto sería mucho más rápido en un sistema integrado que cualquiera de las otras respuestas correctas.

Ben Voigt
fuente
El resto siempre devolverá 0, no es necesario calcular nada.
Ruud Althuizen
@ Ruud: no es cierto. Considere x = -45 e y = 4
Ben Voigt
7

Hay una solución tanto para positivo como negativo, xpero solo para positivo ycon solo 1 división y sin ramas:

int ceil(int x, int y) {
    return x / y + (x % y > 0);
}

Tenga en cuenta que si xes positivo, entonces la división es hacia cero, y debemos agregar 1 si el recordatorio no es cero.

Si xes negativo, entonces la división es hacia cero, eso es lo que necesitamos, y no agregaremos nada porque x % yno es positivo

RiaD
fuente
interesante, porque hay casos comunes con y siendo constante
Wolf
1
mod requiere división, por lo que no es solo 1 división aquí, sino que tal vez el compilador pueda optimizar dos divisiones similares en una.
M.kazem Akhgary
4

Esto funciona para números positivos o negativos:

q = x / y + ((x % y != 0) ? !((x > 0) ^ (y > 0)) : 0);

Si hay un resto, comprueba si xy yson del mismo signo, y añade 1en consecuencia.

Mark Conway
fuente
3

Hubiera preferido comentar pero no tengo un representante lo suficientemente alto.

Hasta donde yo sé, para argumentos positivos y un divisor que es una potencia de 2, esta es la forma más rápida (probado en CUDA):

//example y=8
q = (x >> 3) + !!(x & 7);

Solo para argumentos positivos genéricos, tiendo a hacerlo así:

q = x/y + !!(x % y);
Anroca
fuente
Sería interesante ver cómo se q = x/y + !!(x % y);compara q = x/y + (x % y == 0);y las q = (x + y - 1) / y;soluciones en cuanto al rendimiento en CUDA contemporáneo.
Greg Kramida
-2

Compilar con O3, el compilador realiza bien la optimización.

q = x / y;
if (x % y)  ++q;
dhb
fuente