C ++ Лучший способ получить целочисленное деление и остаток


103

Мне просто интересно, если я хочу разделить a на 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
Третий вариант - ПЛОХОЙ: что, если 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
Я думаю, что многие люди еще в начальной школе знают, что, делая деление, вы получаете остаток бесплатно. Настоящий вопрос: достаточно ли умен наши компиляторы, чтобы воспользоваться этим?

1
@jdv: Я бы не удивился. Это очень простая оптимизация.
Джон Парди

68
Я попробовал провести быстрый тест. В g ++ 4.3.2 с использованием -O2 вывод ассемблера ясно показывает это с использованием одной idivlинструкции и с использованием результатов в eax и edx. Если бы это было не так, я был бы шокирован.
Фред Ларсон

2
@EuriPinhollow Согласитесь, это не вопрос x86. Я привел это только в качестве примера с весьма сомнительным предположением, что другие архитектуры, вероятно, делают нечто подобное.
cnicutar 08

3
Тем не менее, вам нужно указать компилятору оптимизировать, по крайней мере, для g ++. Простой эксперимент с g ++ 6.3.0 на x86_64 показал, что без оптимизации вы получаете две idivlинструкции, но с -O1или выше вы получаете одну. Как сказано в руководстве: «Без какой-либо возможности оптимизации… Заявления независимы» .
Tom Zych

81

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 05

10

Пример кода тестирования div () и комбинированного деления и мода. Я скомпилировал их с помощью 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, а a% 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.