Teto rápido de uma divisão inteira em C / C ++

262

Dados os valores inteiros xe y, C e C ++ retornam como quociente q = x/yo piso do equivalente em ponto flutuante. Estou interessado em um método de devolver o teto. Por exemplo, ceil(10/5)=2e ceil(11/5)=3.

A abordagem óbvia envolve algo como:

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

Isso requer uma comparação e multiplicação extras; e outros métodos que eu vi (usados ​​de fato) envolvem a transmissão como um floatou double. Existe um método mais direto que evite a multiplicação adicional (ou uma segunda divisão) e ramificação, e também evite a conversão como um número de ponto flutuante?

e e
fonte
70
a instrução de divisão, muitas vezes retorna tanto quociente eo resto ao mesmo tempo por isso não há necessidade de multiplicar, apenas q = x/y + (x % y != 0);é suficiente
phuclv
2
@ LưuVĩnhPhúc esse comentário deve ser a resposta aceita, imo.
Andreas Grapentin
1
@ LưuVĩnhPhúc Sério, você precisa adicionar isso como resposta. Eu apenas usei isso para minha resposta durante um teste de codilidade. Funcionou como um encanto, embora eu não tenha certeza de como a parte mod da resposta funciona, mas fez o trabalho.
Zachary Kraus
2
@AndreasGrapent na resposta abaixo de Miguel Figueiredo foi enviada quase um ano antes de Lưu Vĩnh Phúc deixar o comentário acima. Embora eu entenda o quão atraente e elegante é a solução de Miguel, não estou inclinado a mudar a resposta aceita até o momento. Ambas as abordagens permanecem sólidas. Se você se sente suficientemente à vontade, sugiro que demonstre seu apoio votando a resposta de Miguel abaixo.
26514
1
Estranho, eu não vi nenhuma medida ou análise sensata das soluções propostas. Você fala sobre velocidade quase instantaneamente, mas não há discussão de arquiteturas, tubulações, instruções de ramificação e ciclos de relógio.
Rado

Respostas:

394

Para números positivos

unsigned int x, y, q;

Para arredondar ...

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

ou (evitando o estouro em x + y)

q = 1 + ((x - 1) / y); // if x != 0
Sparky
fonte
6
@bitc: Para números negativos, acredito que o C99 especifique arredondado a zero, assim x/ycomo o teto da divisão. O C90 não especificou como arredondar e também não acho que o padrão C ++ atual.
precisa
6
Veja a publicação de Eric Lippert: stackoverflow.com/questions/921180/c-round-up/926806#926806
Mashmagar
3
Nota: Isso pode estourar. q = ((long long) x + y - 1) / y não será. Porém, como meu código é mais lento, se você souber que seus números não excederão, use a versão do Sparky.
Jørgen Fogh
1
@bitc: Eu acredito que o ponto de David foi que você não usaria o cálculo acima se o resultado fosse negativo - você usaria apenasq = x / y;
caf
12
O segundo tem um problema em que x é 0. ceil (0 / y) = 0, mas retorna 1.
Omry Yadan
78

Para números positivos:

    q = x/y + (x % y != 0);
Miguel Figueiredo
fonte
5
instrução de divisão de arquitetura mais comum inclui ainda restante em seu resultado de modo que este realmente precisa de apenas uma divisão e seria muito rápido
phuclv
58

A resposta de Sparky é uma maneira padrão de resolver esse problema, mas como também escrevi no meu comentário, você corre o risco de estouros. Isso pode ser resolvido usando um tipo mais amplo, mas e se você quiser dividir long longs?

A resposta de Nathan Ernst fornece uma solução, mas envolve uma chamada de função, uma declaração de variável e uma condicional, o que o torna não mais curto que o código dos OPs e provavelmente ainda mais lento, porque é mais difícil de otimizar.

Minha solução é esta:

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

Será um pouco mais rápido que o código de OPs, porque o módulo e a divisão são executados usando a mesma instrução no processador, porque o compilador pode ver que eles são equivalentes. Pelo menos o gcc 4.4.1 executa essa otimização com o sinalizador -O2 em x86.

Em teoria, o compilador pode incorporar a chamada de função no código de Nathan Ernst e emitir a mesma coisa, mas o gcc não fez isso quando o testei. Isso pode ocorrer porque vincularia o código compilado a uma única versão da biblioteca padrão.

Como observação final, nada disso importa em uma máquina moderna, exceto se você estiver em um loop extremamente rígido e todos os seus dados estiverem em registradores ou no cache L1. Caso contrário, todas essas soluções serão igualmente rápidas, exceto, possivelmente, as de Nathan Ernst, que podem ser significativamente mais lentas se a função precisar ser buscada na memória principal.

Jørgen Fogh
fonte
3
Houve uma maneira mais fácil de corrigir excesso, simplesmente reduzir y / y:q = (x > 0)? 1 + (x - 1)/y: (x / y);
Ben Voigt
-1: é uma maneira ineficiente, pois negocia um preço baixo * por uma% cara; pior que a abordagem do OP.
usar o seguinte código
2
Não, não tem. Como expliquei na resposta, o operador% é gratuito quando você já realiza a divisão.
Jørgen Fogh
1
Então q = x / y + (x % y > 0);é mais fácil do que ? :expressão?
Han
Depende do que você quer dizer com "mais fácil". Pode ou não ser mais rápido, dependendo de como o compilador o traduz. Meu palpite seria mais lento, mas eu teria que medi-lo para ter certeza.
Jørgen Fogh
18

Você pode usar a divfunção no cstdlib para obter o quociente e o restante em uma única chamada e, em seguida, manipular o teto separadamente, como abaixo

#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
fonte
12
Como um caso interessante da dupla bang, você poderia também return res.quot + !!res.rem;:)
Sam Harwell
O ldiv nem sempre promove os argumentos em longos longos? E isso não custa nada, subir ou descer o elenco?
einpoklum
12

Que tal agora? (requer y não negativo, portanto, não use isso em casos raros em que y é uma variável sem garantia de não negatividade)

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

Reduzi y/ya um, eliminando o termo x + y - 1e com ele qualquer chance de transbordar.

Evito x - 1envolver quando xé um tipo não assinado e contém zero.

Para assinado x, negativo e zero ainda combinam em um único caso.

Provavelmente não é um grande benefício para uma CPU moderna de uso geral, mas isso seria muito mais rápido em um sistema incorporado do que qualquer outra resposta correta.

Ben Voigt
fonte
Seu resto sempre retornará 0, sem necessidade de calcular nada.
Ruud Althuizen
@ Ruud: não é verdade. Considere x = -45 ey = 4
Ben Voigt
7

Existe uma solução para positivo e negativo, xmas apenas para positivo ycom apenas 1 divisão e sem ramificações:

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

Observe que se xfor positivo, a divisão será em direção a zero e devemos adicionar 1 se o lembrete não for zero.

Se xfor negativo, a divisão é em direção a zero, é disso que precisamos e não adicionaremos nada porque x % ynão é positivo

RiaD
fonte
interessante, porque há casos comuns com y ser constante
Lobo
1
O mod requer divisão, portanto, não é apenas uma divisão aqui, mas talvez o complier possa otimizar duas divisões semelhantes em uma.
M.kazem Akhgary
4

Isso funciona para números positivos ou negativos:

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

Se houver um restante, verifica se há xe ytem o mesmo sinal e adiciona de 1acordo.

Mark Conway
fonte
3

Eu preferiria comentar, mas não tenho um representante suficientemente alto.

Tanto quanto sei, para argumentos positivos e um divisor que é uma potência de 2, esta é a maneira mais rápida (testada no CUDA):

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

Apenas para argumentos positivos genéricos, costumo fazer o seguinte:

q = x/y + !!(x % y);
Anroca
fonte
Seria interessante ver como as q = x/y + !!(x % y);pilhas se comparam q = x/y + (x % y == 0);e as q = (x + y - 1) / y;soluções em termos de desempenho na CUDA contemporânea.
Greg Kramida
-2

Compilar com O3, O compilador executa bem a otimização.

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