Вычитание без знака минус в C

Как я могу вычесть два целых числа в C без оператора -?


person SyncMaster    schedule 31.03.2009    source источник
comment
Зачем вам это делать, или это домашнее задание?   -  person Toon Krijthe    schedule 31.03.2009
comment
Что вы вычитаете? целые числа, числа с плавающей запятой?   -  person dan gibson    schedule 31.03.2009
comment
нет, это не домашнее задание .. я столкнулся с этим вопросом в интервью и подумал задать его здесь   -  person SyncMaster    schedule 31.03.2009
comment
Возможный дубликат вычитания двух чисел без использования оператора '-'   -  person MD XF    schedule 21.02.2017
comment
@MDXF Не может быть дублирован. Этот вопрос старше. Если, то связанный вопрос является дубликатом. Но, на мой взгляд, это не потому, что OP в связанном вопросе задал проблему в его / ее коде, а не в способах выполнения вычитания без -. Название немного вводит в заблуждение.   -  person RobertS supports Monica Cellio    schedule 30.05.2020


Ответы (16)


int a = 34;
int b = 50;

Вы можете преобразовать b в отрицательное значение, используя отрицание и добавив 1:

int c = a + (~b + 1);

printf("%d\n", c);

-16

Это отрицание знака дополнения до двух. Процессор делает это, когда вы используете оператор '-', когда вы хотите отрицать значение или вычитать его.

Преобразование float проще. Просто отмените первый бит (Shoosh дал вам пример, как это сделать).

РЕДАКТИРОВАТЬ:

Хорошо, парни. Я сдаюсь. Вот моя версия, независимая от компилятора:

#include <stdio.h>

unsigned int adder(unsigned int a, unsigned int b) {
    unsigned int loop = 1;
    unsigned int sum  = 0;
    unsigned int ai, bi, ci;

    while (loop) {
        ai = a & loop;
        bi = b & loop;
        ci = sum & loop;
        sum = sum ^ ai ^ bi;      // add i-th bit of a and b, and add carry bit stored in sum i-th bit
        loop = loop << 1;
        if ((ai&bi)|(ci&ai)|(ci&bi)) sum = sum^loop; // add carry bit
    }

    return sum;
}

unsigned int sub(unsigned int a, unsigned int b) {
    return adder(a, adder(~b, 1));    // add negation + 1 (two's complement here)
}


int main() {
    unsigned int a = 35;
    unsigned int b = 40;

    printf("%u - %u = %d\n", a, b, sub(a, b)); // printf function isn't compiler independent here

    return 0;
}

Я использую unsigned int, чтобы любой компилятор относился к нему одинаково.

Если вы хотите вычесть отрицательные значения, сделайте это так:

 unsgined int negative15 = adder(~15, 1);

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

person klew    schedule 31.03.2009
comment
это при условии, что базовая HW реализует подобное вычитание. Это хорошее предположение, но в интервью оно может оказаться ложным. - person Nathan Fellman; 31.03.2009
comment
Я не знаю оборудования, которое бы это не поддерживало. Отрицание - это очень простая битовая операция (NAND). Добавление - это несколько соединенных вместе XOR и AND (оба могут быть выполнены с NAND). Трудно представить что-то более простое. - person klew; 31.03.2009
comment
Пункт 2 стандарта 6.2.6.2 стандарта C99 TC2 утверждает, что существует три возможных представления отрицания для целых чисел. Только один - это два дополнения. open-std.org/jtc1/sc22/wg14/ www / docs / n1124.pdf - person Pontus Gagge; 31.03.2009
comment
Прочтите внимательно, там сказано о двух возможных представлениях: два и одно дополнение. Единственная разница заключается в интерпретации битов результата. Если вы интерпретируете мой результат как дополнение, вы получите неверный результат. Но интерпретация выполняется по результату печати, а не во время работы бункера. - person klew; 31.03.2009
comment
Интересный подход ... Должен признаться, я полностью озадачен тем, как printf () `` знает '', как компенсировать нестандартный результат, если ваш компилятор вместо этого использует дополнение или даже знак и величину ... или вы что-то оставили вне? - person Pontus Gagge; 31.03.2009
comment
Извините, вы правы, есть три представления, я неправильно их понял. - person klew; 31.03.2009
comment
Как вы заметили, printf () по-прежнему не гарантирует распечатки правильного ответа, а sub (a, b) + b не обязательно a на всех архитектурах. Но, по крайней мере, вы получаете битовый образец для ответа с двумя дополнениями ... тьфу, сложно! - person Pontus Gagge; 01.04.2009
comment
Если хотите, я могу также написать функцию печати для двух дополнений. Это очень просто. И, поскольку мой ответ дает функции сумматора и подпрограммы, вам не разрешено что-то добавлять, используя + :). - person klew; 02.04.2009

Понтус прав, дополнение 2 не требуется стандартом C (даже если это де-факто стандарт оборудования). +1 за творческие ответы Фила; вот еще один подход к получению -1 без использования стандартной библиотеки или оператора -.

C требует трех возможных представлений, поэтому вы можете понюхать, что работает, и получить разные -1 для каждого:

negation= ~1;
if (negation+1==0)                 /* one's complement arithmetic */
    minusone= ~1;
else if (negation+2==0)            /* two's complement arithmetic */
    minusone= ~0;
else                               /* sign-and-magnitude arithmetic */
    minusone= ~0x7FFFFFFE;

r= a+b*minusone;

Значение 0x7FFFFFFFE будет зависеть от ширины (количества «битов значения») интересующего вас типа целого числа; если не указано иное, у вас есть еще работа, чтобы выяснить это!

person bobince    schedule 31.03.2009
comment
Хорошее решение. Что касается вашего последнего комментария о знаке и величине, я думаю, вы могли бы сделать следующее для любого количества бит в беззнаковом minusone: minusone = ((~1 << 1) >> 1); - person tomlogic; 09.06.2010
comment
~1<<1 приводит к неопределенному поведению (целочисленное переполнение со знаком), а последующий >>1 имеет поведение, определяемое реализацией. - person R.. GitHub STOP HELPING ICE; 13.08.2010
comment
Попробуйте: #define minusone (~1+1==0 ? ~1 : ~1+2==0 ? ~0 : ~(INT_MAX/2*2)) - person R.. GitHub STOP HELPING ICE; 13.08.2010

  • + Без установки битов
  • + Независимость от языка
  • + Может быть настроен для разных типов чисел (int, float и т. Д.)
  • - Почти наверняка не ответ вашего домашнего задания на C (который, скорее всего, будет о битах)

Разверните a-b:

a-b = a + (-b)
    = a + (-1).b

Производство -1:

float:             pi = asin(1.0);
(with    minusone_flt = sin(3.0/2.0*pi);
math.h)           or  = cos(pi)
                  or  = log10(0.1)
complex: minusone_cpx = (0,1)**2; // i squared
integer: minusone_int = 0; minusone_int--; // or convert one of the floats above
person Phil H    schedule 31.03.2009
comment
@tomlogic: Ну, не совсем. Это не - (- foo), а вызов оператора декремента - для целочисленной переменной minusone_int. Вероятно, вы можете вызвать его напрямую, если знаете псевдоним. - person Phil H; 10.06.2010
comment
Все ваши примеры с плавающей запятой зависят от точности реализации. Некоторые из них могут не работать из-за ошибок округления; единственный, которому я отдаленно доверяю, - это asin(1.0), поскольку он включает только точные аргументы. - person R.. GitHub STOP HELPING ICE; 13.08.2010
comment
Cos (2pi) равен 1, я думаю, вы думали о cos (pi) - person micmoo; 13.08.2010
comment
Целое число использует -. - person S.S. Anne; 02.04.2020

  • + Без установки битов
  • + Независимость от языка
  • + Независимо от типа числа (int, float и т. Д.)
  • - Требуется a> b (т. е. положительный результат)
  • - Почти наверняка не ответ вашего домашнего задания на C (который, скорее всего, будет о битах)
  • a - b = c

    ограничиваясь числовым пространством 0 ‹= c‹ (a + b):

           (a - b) mod(a+b) = c mod(a+b)
    a mod(a+b) - b mod(a+b) = c mod(a+b)
    

    упрощая второй член:

    (-b).mod(a+b) = (a+b-b).mod(a+b)
                  = a.mod(a+b)
    

    заменяя:

    a.mod(a+b) + a.mod(a+b) = c.mod(a+b)
    2a.mod(a+b) = c.mod(a+b)
    

    если b> a, то b-a> 0, поэтому:

    c.mod(a+b) = c
    c = 2a.mod(a+b)
    

    Итак, если a всегда больше b, тогда это сработает.

    person Phil H    schedule 31.03.2009
    comment
    Но вы предполагаете, что дойдете до последнего шага! Наверняка вы имеете в виду Если a ›b, то a-b› 0. - person Pontus Gagge; 31.03.2009
    comment
    Вы должны увидеть мой другой ответ! Еще лучше и даже менее ограниченно! - person Phil H; 31.03.2009
    comment
    @Phil H: Да, но этот ответ намного элегантнее (и поначалу утомляет мозг)! - person Pontus Gagge; 31.03.2009
    comment
    Модуло действительно доставляет огромную боль между ушами! - person Phil H; 31.03.2009
    comment
    О, просто взгляните на полиномиальные модули по полям - это чистое развлечение (после того, как ваш мозг завершит перезагрузку). - person Pontus Gagge; 01.04.2009

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

    int subtract(int a, int b)
    {
      if ( b < 0 )
        return a+abs(b);
      while (b-- > 0)
        --a;
      return a;
    }
    

    Глупый вопрос ... наверное глупое интервью!

    person Pontus Gagge    schedule 31.03.2009
    comment
    На самом деле, это неплохой вопрос для собеседования. Правильный ответ показал бы близкое знакомство как с языком, так и с оборудованием, а также некоторое нестандартное мышление. Это звучит глупо только потому, что сформулировано как загадка. - person shoosh; 31.03.2009
    comment
    Ответ прост, если у вас были уроки логической алгебры :) - person klew; 31.03.2009
    comment
    Кстати, вы используете оператор '-', но скрываете его в операторе '-'. - person klew; 31.03.2009
    comment
    ‹LanguageLawyer› Нет, два отдельных оператора по определению. Токены просто представлены одними и теми же кодами ASCII! (Неустановленное условие в вопросе состоит в том, запрещен ли только двоичный '-' или также унарный '-'.) ‹/LanguageLawyer› - person Pontus Gagge; 31.03.2009
    comment
    Согласно вашему ответу, я могу написать функцию subtract (int a, int b) {return a-b;}, поместить ее в библиотеку и использовать в своем коде. '-' - это вычитание: some_value - 1. Хм, может, будет проще использовать оператор - =? :) - person klew; 31.03.2009
    comment
    @klew: посмотрите на это с другой стороны, во что --a будет транслироваться машинный код на x86? А как насчет a - = b? DEC и SUB соответственно. Они одинаковы? Они могут использовать один и тот же микрокод, но определенно иметь разные коды операций. - person Pontus Gagge; 31.03.2009
    comment
    @klew: Посмотрите на (отредактированный) вопрос: он просит нас не использовать ни одну из форм оператора '-'. Если вы можете найти стандартную библиотечную функцию для вычитания, это тоже будет приемлемым ответом. - person Pontus Gagge; 31.03.2009
    comment
    Кстати, если вы уменьшите значение ниже 0, вы получите значения в дополнении до двух :). Дополнение до единицы имеет отрицательный 0 и наверняка не используется ни в каких операциях добавления и подгруппы. И интерпретация этих битовых значений - это не проблема аппаратного обеспечения, а проблема программного обеспечения. - person klew; 31.03.2009
    comment
    @klew: Ага. На некоторых архитектурах, например x86. И на некоторых это будет дополнение, а на некоторых это будет знак + величина, что касается стандарта C. Или вы заявляете о всеобщем принятии дополнения до двух (например, порядка байтов с прямым порядком байтов)? - person Pontus Gagge; 31.03.2009
    comment
    Да, два дополнения должны править миром :). Сегодня я задал вопрос, знает ли кто-нибудь современный компилятор C, который дает не два дополнительных числа, и я не получил ответа. Может, знаете что-нибудь? - person klew; 02.04.2009
    comment
    '-' подразумевает '-'. Я бы не принял это в режиме языкового юриста. - person Michael Foukarakis; 06.07.2010
    comment
    @ Майкл: это твоя привилегия. Аналогично, например, в Ассемблер x86, DEC и SUB, вероятно, используют один и тот же или связанный микрокод для ALU, но это разные коды операций. ИМХО, у @bobince здесь лучший ответ. Глупые вопросы должны получать глупые ответы! - person Pontus Gagge; 06.07.2010
    comment
    Согласен, хотя я думаю, что это четкий и окончательный ответ - совсем не глупый. Идея вопросов на собеседовании вроде этого - глупая. ;-) - person Michael Foukarakis; 06.07.2010
    comment
    Если - разрешено, то ответ далек от оптимального, и вы, вероятно, не пройдете собеседование. Использование - лучше сделать: a + (0 -) * b - person Uri; 23.03.2011
    comment
    Спасибо, Ури: Я уверен, что интервьюеры всегда думали об эффективности ... Глупые вопросы заслуживают, нет, требуют глупых ответов. - person Pontus Gagge; 23.03.2011

    Если вы хотите сделать это для чисел с плавающей запятой, начните с положительного числа и измените его бит знака следующим образом:

    float f = 3;
    *(int*)&f |= 0x80000000;
    // now f is -3.
    float m = 4 + f; 
    // m = 1
    

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

    person shoosh    schedule 31.03.2009
    comment
    Повторяйте за мной: неопределенное поведение. Этот вопрос для интервью, кажется, позволяет отделить тех, кто выполняет дела, от тех, кто делает то, что (гарантированно) работает! - person Pontus Gagge; 31.03.2009
    comment
    Покажите мне современный процессор с числами с плавающей запятой, не соответствующими стандарту IEEE-754. Этот вопрос отделяет здравомыслящего от анального удерживающего. - person shoosh; 31.03.2009
    comment
    @shoosh. Возможно. Или тех, кого укусили их предположения от тех, кому еще предстоит. Зависит от того, кого ищут интервьюеры. (Имейте в виду, что взлом с неопределенным поведением иногда является правильным и правильным: я просто думаю, что те времена были исключением.) - person Pontus Gagge; 31.03.2009
    comment
    @ klew / shoosh: Хммм ... перепроверив, это могло бы быть на самом деле безопаснее, по стандарту, чем бит-тидлинг целые. Все еще не уверен в проблемах порядка байтов, но IEEE-754 обеспечивает абстракцию от аппаратных зависимостей. Мои извинения. Хотя обсуждение интересное. - person Pontus Gagge; 31.03.2009
    comment
    @Pontus: с ints то же самое. Это будет работать даже на процессоре, который имеет только операции добавления и биты. Дополнение до двух - это только способ, которым программа интерпретирует биты. И это будет работать до тех пор, пока мы будем использовать логическую алгебру. [...] - person klew; 31.03.2009
    comment
    [...] Даже если ваш компилятор заставит вас использовать одно дополнение, вы все равно можете написать свою собственную функцию, чтобы правильно печатать ее как два дополнения - person klew; 31.03.2009
    comment
    @klew: Незначительная придирка: дискретная математика, да, но только периферийная логическая алгебра как таковая ... Что касается целых чисел, это обсуждение относится к другому ответу (с волшебной функцией printf ()!). - person Pontus Gagge; 31.03.2009

    Для вычитания в C двух целых чисел вам понадобится:

    int subtract(int a, int b)
    {
        return a + (~b) + 1;
    }
    

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

    person Ionel Bratianu    schedule 31.03.2009
    comment
    Пункт 2 стандарта 6.2.6.2 стандарта C99 TC2 утверждает, что существует три возможных представления отрицания для целых чисел. Только один - это два дополнения. open-std.org/jtc1/sc22/wg14/ www / docs / n1124.pdf - person Pontus Gagge; 31.03.2009
    comment
    Таким образом, этот ответ будет работать в большинстве реализаций, но на самом деле использует неопределенное поведение, которое может взорваться в будущем. - person Pontus Gagge; 31.03.2009

    Я полагаю это

    b - a = ~( a + ~b)

    person jonny    schedule 31.03.2009
    comment
    Пункт 2 стандарта 6.2.6.2 стандарта C99 TC2 утверждает, что существует три возможных представления отрицания для целых чисел. Только один - это два дополнения. open-std.org/jtc1/sc22/wg14/ www / docs / n1124.pdf - person Pontus Gagge; 31.03.2009

    Тип сборки (аккумулятора):

    int result = a;
    result -= b;
    
    person starblue    schedule 31.03.2009
    comment
    Вы все еще используете двоичный оператор '-'. - person Liudvikas Bukys; 31.03.2009
    comment
    Нет. - = это другой оператор. - person recursive; 13.08.2010

    Поскольку вопрос задан для целых чисел, а не ints, вы можете реализовать небольшой интерпретатор, который использует церковные числа.

    person Pete Kirkham    schedule 31.03.2009

    Создайте таблицу поиска для всех возможных случаев int-int!

    person Jotux    schedule 03.04.2009
    comment
    Помимо обычных голосов, каждый вопрос должен иметь LOL-подсчет голосов;) - person el.pescado; 06.07.2010

    Не испытано. Без использования дополнения до 2:

    #include <stdlib.h>
    #include <stdio.h>
    int sillyNegate(int x) {
       if (x <= 0)
         return abs(x);
       else {
         // setlocale(LC_ALL, "C"); // if necessary.
         char buffer[256];
         snprintf(buffer, 255, "%c%d", 0x2d, x);
         sscanf(buffer, "%d", &x);
         return x;
       }
    }
    

    Предполагая, что длина int намного меньше 255, и передача snprintf / sscanf туда и обратно не приведет к неопределенному поведению (верно? Верно?).

    Вычитание можно вычислить с помощью a - b == a + (-b).


    Альтернатива:

    #include <math.h>
    int moreSillyNegate(int x) {
       return x * ilogb(0.5);  // ilogb(0.5) == -1;
    }
    

    person kennytm    schedule 09.06.2010

    Это будет работать с использованием целочисленного переполнения:

    #include<limits.h>    
    int subtractWithoutMinusSign(int a, int b){
             return a + (b * (INT_MAX + INT_MAX + 1));
    }
    

    Это также работает для поплавков (при условии, что вы создаете версию с плавающей запятой…)

    person micmoo    schedule 13.08.2010
    comment
    Это имеет неопределенное поведение. Тем не менее, это сработало бы: return a + b * (INT_MIN + INT_MAX); ;-) - person R.. GitHub STOP HELPING ICE; 13.08.2010

    Для максимального диапазона любого типа данных дополнение до единицы обеспечивает отрицательное значение, уменьшенное на 1 до любого соответствующего значения. пример:
    ~ 1 --------> -2
    ~ 2 ---------> -3
    и так далее ... Я покажу вы это наблюдение, используя небольшой фрагмент кода

    #include<stdio.h>
    int main()
    {
       int a , b;
       a=10;
       b=~a; // b-----> -11    
       printf("%d\n",a+~b+1);// equivalent to a-b
       return 0;
    }
    

    Вывод: 0
    Примечание. Это действительно только для диапазона типов данных. означает, что для типа данных int это правило будет применяться только для значения диапазона [-2 147 483 648 до 2 147 483 647]. Спасибо ..... Может это вам поможет

    person Minion    schedule 06.03.2017

    Iff:

    1. Minuend больше или равно 0, или
    2. Вычитаемое больше или равно 0, или
    3. Вычитаемое и Минус меньше 0

    умножьте Minuend на -1 и добавьте результат к Subtrahend:

    SUB + (MIN * -1)
    

    В противном случае умножьте Minuend на 1 и добавьте результат к Subtrahend.

    SUB + (MIN * 1)
    

    Пример (Попробуйте онлайн):

    #include <stdio.h>
    
    int subtract (int a, int b)
    {
        if ( a >= 0 || b >= 0 || ( a < 0 && b < 0 ) )
        {
            return a + (b * -1);
        }
    
        return a + (b * 1); 
    }
    
    int main (void)
    {
        int x = -1;
        int y = -5;
        printf("%d - %d = %d", x, y, subtract(x, y) );
    }
    

    Вывод:

    -1 - -5 = 4
    
    person RobertS supports Monica Cellio    schedule 30.05.2020

    person    schedule
    comment
    Учитывая вопрос и его теги, я не верю, что C ++ / CLI и использование классов .Net является допустимым решением, хотя я согласен с тем, что вы использовали это только для ввода-вывода - возможно, ненужное уточнение? Также это решение имеет два экземпляра оператора -, так что вряд ли это вообще решение! - person Clifford; 09.06.2010