C ++ Найкращий спосіб отримати ціле ділення та залишок


103

Мені просто цікаво, чи хочу я розділити на b, і мене цікавить як результат c, так і залишок (наприклад, скажіть, що у мене є кількість секунд і я хочу розділити це на хвилини і секунди), який найкращий спосіб піти про це?

Було б

int c = (int)a / b;
int d = a % b;

або

int c = (int)a / b;
int d = a - b * c;

або

double tmp = a / b;
int c = (int)tmp;
int d = (int)(0.5+(tmp-c)*b);

або

можливо, є магічна функція, яка дає одночасно і те й інше?


5
всі наведені нижче відповіді здаються розумними, я хотів би лише додати, що будь-яке спілкування з doubleвашим (останній пункт) мені здається поганою ідеєю, ви отримаєте цифри, які не вирівнюються, і можуть коштувати вам на продуктивність і виконуваний розмір (завжди був проблемою для мене в певних вбудованих системах).
nhed

2
Третій варіант - BAD: що робити, якщо tmp = 54,999999999999943157? Сказане, кастинг у старому стилі - це ніколи не розумна справа.
jimifiki

Відповіді:


98

На x86 решта - це побічний продукт самого поділу, тому будь-який напівпристойний компілятор повинен мати можливість просто використовувати його (а не виконувати divзнову). Можливо, це робиться і в інших архітектурах.

Інструкція: DIVsrc

Примітка: Непідписаний відділ. Ділить акумулятор (AX) на "src". Якщо дільник є значенням байта, результат ставиться на AL, а решта - на AH . Якщо дільник є значенням слова, то DX: AX ділиться на "src", а результат зберігається в AX, а решта зберігається в DX .

int c = (int)a / b;
int d = a % b; /* Likely uses the result of the division. */

9
Я думаю, що багато хто з poeple знає з початкової школи, що, роблячи ділення, ви отримуєте залишок безкоштовно. Справжнє питання: чи достатньо розумні наші компілятори, щоб скористатися цим?

1
@jdv: Я би не здивувався. Це дуже проста оптимізація.
Джон Перді

68
Я спробував швидкий тест. При використанні g ++ 4.3.2 за допомогою -O2 висновок асемблера чітко показує його за допомогою однієї idivlінструкції та використання результатів у eax та edx. Я був би в шоці, якби цього не сталося.
Фред Ларсон

2
@EuriPinhollow Погодьтеся, це не питання щодо x86. Я наводив це лише як приклад, з дуже сумнівним припущенням, що інші архітектури, ймовірно, роблять щось подібне.
cnicutar

3
Вам потрібно сказати компілятору, щоб оптимізувати, хоча б для g ++. Простий експеримент з g ++ 6.3.0 на x86_64 показав, що без оптимізації взагалі ви отримуєте дві idivlінструкції, але -O1отримуєте одну або більше. Як зазначено в посібнику, "Без жодної опції оптимізації ... Заяви незалежні" .
Том Зіч

80

std::div повертає структуру як з результатом, так і з залишком.


5
Мені цікаво дізнатися, чи це насправді ефективніше, ніж, скажімо, варіант 1 на сучасному компіляторі.
Грег Хоуелл

2
Приємно, я не знав. Це швидше?

Приємно. Чи траплялось би вам знати, чи реалізується це довго-довго десь?
Cookie

@Cookie: C ++ 03 не має поняття long long, але велика ймовірність, що ваш компілятор має long longперевантаження std::divяк розширення.
ildjarn

@Greg: Я не думаю, що це є, враховуючи, що він, швидше за все, повинен записувати результати в структуру в пам'ять і повертати її, що (якщо вона не компілюється як вбудований і не вдалося оптимізувати доступ до структури) - це більша кара, ніж виконання додаткова інструкція про поділ.
pezcode

28

Щонайменше на x86, g ++ 4.6.1 просто використовує IDIVL і отримує обидва з цієї однієї інструкції.

Код C ++:

void foo(int a, int b, int* c, int* d)
{
  *c = a / b;
  *d = a % b;
}

код x86:

__Z3fooiiPiS_:
LFB4:
    movq    %rdx, %r8
    movl    %edi, %edx
    movl    %edi, %eax
    sarl    $31, %edx
    idivl   %esi
    movl    %eax, (%r8)
    movl    %edx, (%rcx)
    ret

Чи має значення замовлення? Наприклад, якщо ви повторюєте ідентифікатор /=- вам може знадобитися використовувати тимчасову змінну, щоб спочатку зберегти поділ.
AnnanFay

@Annan Це не мало значення тоді, і це не сьогодні. Компілятори досить розумні, щоб переупорядкувати їх.
Sahsahae

10

Приклад тестування коду div () та комбінованого поділу & mod. Я компілював їх за допомогою gcc -O3, мені довелося додати виклик doNothing, щоб зупинити компілятор не оптимізувати все (вихід буде 0 для рішення розділення + мод).

Візьміть його з зерном солі:

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>

extern doNothing(int,int); // Empty function in another compilation unit

int main() {
    int i;
    struct timeval timeval;
    struct timeval timeval2;
    div_t result;
    gettimeofday(&timeval,NULL);
    for (i = 0; i < 1000; ++i) {
        result = div(i,3);
        doNothing(result.quot,result.rem);
    }
    gettimeofday(&timeval2,NULL);
    printf("%d",timeval2.tv_usec - timeval.tv_usec);
}

Виходи: 150

#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>

extern doNothing(int,int); // Empty function in another compilation unit

int main() {
    int i;
    struct timeval timeval;
    struct timeval timeval2;
    int dividend;
    int rem;
    gettimeofday(&timeval,NULL);
    for (i = 0; i < 1000; ++i) {
        dividend = i / 3;
        rem = i % 3;
        doNothing(dividend,rem);
    }
    gettimeofday(&timeval2,NULL);
    printf("%d",timeval2.tv_usec - timeval.tv_usec);
}

Виходи: 25



3

За умови рівності найкращим рішенням є те, що чітко виражає ваші наміри. Так:

int totalSeconds = 453;
int minutes = totalSeconds / 60;
int remainingSeconds = totalSeconds % 60;

це, мабуть, найкращий із трьох представлених вами варіантів. Як зазначається в інших відповідях, divметод обчислить обидва значення одночасно.


3

Тут ви не можете довіряти g ++ 4.6.3 із 64-бітовими цілими числами на 32-бітній платформі Intel. a / b обчислюється викликом до divdi3, а% b обчислюється викликом до moddi3. Я навіть можу привести приклад, який обчислює a / b і ab * (a / b) з цими дзвінками. Тому я використовую c = a / b і ab * c.

Метод div дає виклик функції, яка обчислює структуру div, але виклик функції здається неефективним на платформах, які мають апаратну підтримку інтегрального типу (тобто 64-бітні цілі числа на 64-бітних платформах intel / amd).


-4

Ви можете використовувати модуль, щоб отримати решту. Хоча відповідь @ cnicutar здається чистішою / прямішою.


2
Так, оригінальний плакат використовував оператор модуля, питання полягає в тому, як зробити його ефективним.
Марк Лаката
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.