целочисленное деление со знаком с округлением в C - PullRequest
6 голосов
/ 31 января 2020

Я бы хотел вычислить x / y, где x и y - целые числа со знаком, и получить результат, округленный до ближайшего целого числа. В частности, я хотел бы, чтобы функция rquotient(x, y) использовала целочисленную арифметику c такую, чтобы:

ASSERT(rquotient(59, 4) == 15);
ASSERT(rquotient(59, -4) == -15);
ASSERT(rquotient(-59, 4) == -15);
ASSERT(rquotient(-59, -4) == 15);

ASSERT(rquotient(57, 4) == 14);
ASSERT(rquotient(57, -4) == -14);
ASSERT(rquotient(-57, 4) == -14);
ASSERT(rquotient(-57, -4) == 14);

Я искал решение SO и нашел следующее (у каждого свой недостаток ):

Ответы [ 4 ]

5 голосов
/ 31 января 2020

Если вы знаете, x и y оба положительны:

int rquotient_uu(unsigned int x, unsigned int y) {
  return (x + y/2) / y;
}

Если вы знаете y положительно:

int rquotient_su(int x, unsigned int y) {
  if (x > 0) {
    return (x + y/2) / y;
  } else {
    return (x - y/2) / y;
  }
}

Если оба подписаны :

int rquotient_ss(int x, int y) {
  if ((x ^ y) >= 0) {            // beware of operator precedence
    return (x + y/2) / y;        // signs match, positive quotient
  } else {
    return (x - y/2) / y;        // signs differ, negative quotient
  }
}

И если вы действительно хотите сбить с толку свое будущее или быть зависимым от код-гольфа, не поддавайтесь желанию написать это так:;)

int rquotient_ss(int x, int y) {
  return (x + (((x^y)>=0)?y:-y)/2)/y;
}
3 голосов
/ 31 января 2020

Простым решением будет использование round и double:

#include <math.h>

int rquotient(int const x, int const y) {
    return (int)round((double)x / y);
}
2 голосов
/ 03 февраля 2020

Вот решение, использующее целочисленную арифметику c, которая вычисляет правильный результат для всех значений в определенном диапазоне: x и y может быть любым значением int с y != 0 && !(x == INT_MIN && y == -1).

* 1006. * Другие целочисленные решения ведут себя некорректно для значений, слишком близких к INT_MIN и / или INT_MAX.
// simpler function if x >= 0 and y > 0
int rquotient_UU(int x, int y) {
    int quo = x / y;
    int rem = x % y;
    return quo + (rem > ((y - 1) >> 1));
}

// generic function for y != 0 and !(x == INT_MIN && y == -1)
int rquotient_SS(int x, int y) {
    int quo = x / y;
    int rem = x % y;
    if (rem == 0)
        return quo;
    // quo * y + rem = x
    if (rem > 0) {
        if (y > 0) {
            return quo + (rem > (y - 1) / 2);
        } else {
            return quo - (rem > -((y + 1) / 2));
        }
    } else {
        if (y > 0) {
            return quo - (rem < -((y - 1) / 2));
        } else {
            return quo + (rem < ((y + 1) / 2));
        }
    }
}

Эти функции лишь незначительно медленнее, чем те, которые тестировал Джонатан Леффлер. Я расширил его тестовый стенд, добавив в него отрицательные значения, и получил на своем старом ноутбуке вывод:

rquotient_UU:    9.409108  (278977174548)
rquotient_SS:   12.851408  (278977174548)
rquotient_uu:    8.734572  (278977174548)
rquotient_su:    8.700956  (278977174548)
rquotient_ss:   12.079210  (278977174548)
rquotient_dd:   12.554621  (278977174548)
2 голосов
/ 01 февраля 2020

Сроки предлагаемых решений

Код, представленный здесь, проверяет производительность 3 предложенных функций в ответе на fearless_fool и решение в ответе по Айксан . Функции модифицируются таким образом, чтобы всегда принимать int аргументы (const в int const x не требуется), но тестовый код использует только тестовые значения в диапазоне, где оба значения x и y неотрицательны.

В коде используется набор функций синхронизации, доступных в моем репозитории SOQ (вопросы о переполнении стека) на GitHub в виде файлов timer.c и timer.h в src / libsoq подкаталог.

#define NDEBUG 1

#include "timer.h"
#include <assert.h>
#include <limits.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

/* JL: added static to rquotient_xx functions */

/* JL: removed two const qualifiers */
static
int rquotient_dd(int x, int y)
{
    return (int)round((double)x / y);
}

/* JL: removed unsigned - added assert */
static
int rquotient_uu(int x, int y)
{
    assert(x >= 0 && y > 0);
    return (x + y / 2) / y;
}

/* JL: removed unsigned - added assert */
static
int rquotient_su(int x, int y)
{
    assert(y > 0);
    if (x > 0)
        return (x + y / 2) / y;
    else
        return (x - y / 2) / y;
}

static
int rquotient_ss(int x, int y)
{
    if ((x ^ y) > 0)
        return (x + y / 2) / y;
    else
        return (x - y / 2) / y;
}

typedef int (*Divider)(int x, int y);

static void test_harness(const char *tag, Divider function)
{
    Clock clk;
    unsigned long long accumulator = 0;

    clk_init(&clk);

    clk_start(&clk);
    for (int i = 1; i < INT_MAX / 1024; i += 13)
    {
        int max_div = i / 4;
        if (max_div == 0)
            max_div = 1;
        for (int j = 1; j < max_div; j += 15)
            accumulator += (*function)(i, j);
    }
    clk_stop(&clk);

    char buffer[32];
    printf("%s: %10s  (%llu)\n", tag, clk_elapsed_us(&clk, buffer, sizeof(buffer)), accumulator);
}

int main(void)
{
    for (int i = 0; i < 10; i++)
    {
        test_harness("rquotient_uu", rquotient_uu);
        test_harness("rquotient_su", rquotient_su);
        test_harness("rquotient_ss", rquotient_ss);
        test_harness("rquotient_dd", rquotient_dd);
    }
    return 0;
}

Использование accumulator служит двум важным целям. Во-первых, он проверяет, что разные вычисления дают одинаковые результаты. Во-вторых, это гарантирует, что компилятор не может оптимизировать циклы - накопленное значение должно быть напечатано. Отрадно видеть, что накопленное значение одинаково во всех тестах. Нечетные константы (INT_MAX / 1024, 13, 15) являются предполагаемыми значениями, которые дают разумное время на тестовой машине - они означают, что тесты охватывают довольно много значений, не принимая неоправданно длинных времен.

Результаты тестов производительности

Я провел тесты на MacBook Pro (15 дюймов, 2017 г. - с 2,9 ГГц чипом Intel Core i7 и 16 ГБ 2133 МГц ОЗУ LPDDR3) под управлением MacOS 10.14.6 Mojave, скомпилирован с (самодельный) G CC 9.2.0 и набор инструментов Xcode 11.3.1.

$ gcc -O3 -g -I./inc -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes \
>     iround53.c -o iround53 -L./lib -lsoq 
$

Один набор результатов синхронизации был:

rquotient_uu:   6.272698  (286795780245)
rquotient_su:   6.257373  (286795780245)
rquotient_ss:   6.221263  (286795780245)
rquotient_dd:  10.956196  (286795780245)
rquotient_uu:   6.247602  (286795780245)
rquotient_su:   6.289057  (286795780245)
rquotient_ss:   6.258776  (286795780245)
rquotient_dd:  10.878083  (286795780245)
rquotient_uu:   6.256511  (286795780245)
rquotient_su:   6.286257  (286795780245)
rquotient_ss:   6.323997  (286795780245)
rquotient_dd:  11.055200  (286795780245)
rquotient_uu:   6.256689  (286795780245)
rquotient_su:   6.302265  (286795780245)
rquotient_ss:   6.296409  (286795780245)
rquotient_dd:  10.943110  (286795780245)
rquotient_uu:   6.239497  (286795780245)
rquotient_su:   6.238150  (286795780245)
rquotient_ss:   6.195744  (286795780245)
rquotient_dd:  10.975971  (286795780245)
rquotient_uu:   6.252275  (286795780245)
rquotient_su:   6.218718  (286795780245)
rquotient_ss:   6.241050  (286795780245)
rquotient_dd:  10.986962  (286795780245)
rquotient_uu:   6.254244  (286795780245)
rquotient_su:   6.213412  (286795780245)
rquotient_ss:   6.280628  (286795780245)
rquotient_dd:  10.963290  (286795780245)
rquotient_uu:   6.237975  (286795780245)
rquotient_su:   6.278504  (286795780245)
rquotient_ss:   6.286199  (286795780245)
rquotient_dd:  10.984483  (286795780245)
rquotient_uu:   6.219504  (286795780245)
rquotient_su:   6.208329  (286795780245)
rquotient_ss:   6.251772  (286795780245)
rquotient_dd:  10.983716  (286795780245)
rquotient_uu:   6.369181  (286795780245)
rquotient_su:   6.362766  (286795780245)
rquotient_ss:   6.299449  (286795780245)
rquotient_dd:  11.028050  (286795780245)

При анализе среднее значение и примерное стандартное отклонение для различных функций:

Function       Count   Mean        Standard deviation
rquotient_uu      10    6.260618   0.040679 (sample)
rquotient_su      10    6.265483   0.048249 (sample)
rquotient_ss      10    6.265529   0.039216 (sample)
rquotient_dd      10   10.975506   0.047673 (sample)

Не требуется больших статистических знаний, чтобы увидеть, что по существу нет разницы в производительности между тремя «целочисленными» функциями, потому что разница между три средних - это намного меньше, чем одно стандартное отклонение (и, чтобы быть значительным, оно должно быть больше, чем одно стандартное отклонение). Также не нужно много умения, чтобы заметить, что преобразование в double, деление, округление и обратное преобразование в целое число занимает почти вдвое больше времени, чем целочисленные версии. В прошлые (давние) времена, целочисленные значения против расхождений с плавающей точкой могли быть намного больше. В вычислениях и накоплениях l oop имеется небольшой объем накладных расходов; это увеличило бы несоответствие между вычислениями целых чисел и вычислений с плавающей точкой.

На компьютере, на котором выполнялся тест, в фоновом режиме были открыты различные программы, но не воспроизводилось видео, браузер отображал переполнение стека, а не рекламу. загруженные страницы, и я возился с мобильным телефоном, пока тест выполнялся на ноутбуке. Одна попытка запуска теста, во время которой я переключался между страницами в браузере, показала гораздо большее количество ошибок c (больше времени, когда я использовал браузер, даже если это многоядерный компьютер).

Другие тесты с условием if ((x ^ y) > 0), исправленным на if ((x ^ y) >= 0), дали немного другие результаты синхронизации (но то же значение для accumulator):

rquotient_su     10    6.272791    0.037206
rquotient_dd     10    9.396147    0.047195
rquotient_uu     10    6.293301    0.056585
rquotient_ss     10    6.271035    0.052786

rquotient_su     10    6.187112    0.131749
rquotient_dd     10    9.100924    0.064599
rquotient_uu     10    6.127121    0.092406
rquotient_ss     10    6.203070    0.219747

rquotient_su     10    6.171390    0.133949
rquotient_dd     10    9.195283    0.124936
rquotient_uu     10    6.214054    0.177490
rquotient_ss     10    6.166569    0.138124

Разница в производительности для арифметики с плавающей точкой c не совсем так явно, но все же окончательно в пользу целочисленной арифметики c. В частности, последние из этих тестов предполагают, что во время выполнения тестов на компьютере была какая-то другая активность - хотя это не я смотрел на веб-страницы или что-то в этом роде.


Использование -ffast-math

Ayxan спросил:

Интересно, если бы -ffast-math имел бы значение.

Я перекомпилировал с дополнительным вариант, и это действительно имеет значение. Обратите внимание, что исходный код был скомпилирован с -O3 - он был оптимизирован. Тем не менее, необработанные данные из прогона с -ffast-math были:

rquotient_uu:   6.162182  (286795780245)
rquotient_su:   6.068469  (286795780245)
rquotient_ss:   6.041566  (286795780245)
rquotient_dd:   4.568538  (286795780245)
rquotient_uu:   6.143200  (286795780245)
rquotient_su:   6.071906  (286795780245)
rquotient_ss:   6.063543  (286795780245)
rquotient_dd:   4.543419  (286795780245)
rquotient_uu:   6.115283  (286795780245)
rquotient_su:   6.083157  (286795780245)
rquotient_ss:   6.063975  (286795780245)
rquotient_dd:   4.536071  (286795780245)
rquotient_uu:   6.078680  (286795780245)
rquotient_su:   6.072075  (286795780245)
rquotient_ss:   6.104850  (286795780245)
rquotient_dd:   4.585272  (286795780245)
rquotient_uu:   6.084941  (286795780245)
rquotient_su:   6.080311  (286795780245)
rquotient_ss:   6.069046  (286795780245)
rquotient_dd:   4.563945  (286795780245)
rquotient_uu:   6.075380  (286795780245)
rquotient_su:   6.236980  (286795780245)
rquotient_ss:   6.210127  (286795780245)
rquotient_dd:   4.787269  (286795780245)
rquotient_uu:   6.406603  (286795780245)
rquotient_su:   6.378812  (286795780245)
rquotient_ss:   6.194098  (286795780245)
rquotient_dd:   4.589568  (286795780245)
rquotient_uu:   6.243652  (286795780245)
rquotient_su:   6.132142  (286795780245)
rquotient_ss:   6.079181  (286795780245)
rquotient_dd:   4.595330  (286795780245)
rquotient_uu:   6.070584  (286795780245)
rquotient_su:   6.081373  (286795780245)
rquotient_ss:   6.075867  (286795780245)
rquotient_dd:   4.558105  (286795780245)
rquotient_uu:   6.106258  (286795780245)
rquotient_su:   6.091108  (286795780245)
rquotient_ss:   6.128787  (286795780245)
rquotient_dd:   4.553061  (286795780245)

И статистика из этого:

rquotient_su     10    6.129633    0.101331
rquotient_dd     10    4.588058    0.072669
rquotient_uu     10    6.148676    0.104937
rquotient_ss     10    6.103104    0.057498

Не нужно быть статистическим гением, чтобы заметить, что это показывает, что -ffast-math альтернатива с плавающей запятой теперь лучше, чем целочисленная версия - аналогично тому, как целое число было лучше, чем с плавающей запятой без дополнительной опции компилятора .

Еще один набор статистики с -ffast-math. Они показывают меньшие отклонения (стандартные отклонения), но тот же общий результат.

rquotient_su     10    6.060705    0.024372
rquotient_dd     10    4.543576    0.014742
rquotient_uu     10    6.057718    0.026419
rquotient_ss     10    6.061652    0.034652

Для 32-разрядных целых чисел может показаться, что при -ffast-math код, использующий double, может быть быстрее, чем код с использованием только целых чисел.

Если диапазон был изменен с 32-разрядных целых чисел на 64-разрядные целые числа, то 64-разрядные двойные числа не смогут точно представлять все целочисленные значения. В этот момент, если разделяемые числа достаточно велики, вы можете начать обнаруживать ошибки точности (результаты накопителя могут отличаться). 64-битный дубль фактически имеет 53 бита для представления мантиссы, поэтому, если число битов в целых числах было больше, точность падает.


Тестирование производительности затруднительно. YMMV!

Действительно, было бы безопаснее сказать "Ваше Милеж" WILL Vary ".

...