Имеет ли смысл abs (unsigned long) смысл?

Я столкнулся с этим кодом, который, кстати, мой профилировщик описывает как узкое место:

#include <stdlib.h>

unsigned long a, b;
// Calculate values for a and b
unsigned long c;

c = abs(a - b);

Эта строка делает что-то интересное, что c = a - b;? Использовать ли какой-либо из параметров undefined или поведение, определяемое реализацией, и есть ли другие потенциальные ошибки? Обратите внимание, что включен C <stdlib.h>, а не <cstdlib>.

Ответ 1

Нет, это не имеет смысла.

Если вы хотите разницу, используйте

c = (a > b) ? a - b : b - a;

или

c = max(a, b) - min(a, b);

Без знака, если значение ниже нуля, будет выполнено обратное преобразование (эффект аналогичен добавлению 2 sizeof (unsigned long) * CHAR_BIT)

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

namespace MyUtils {
  template<typename T>
  T diff(const T&a, const T&b) {
    return (a > b) ? (a - b) : (b - a);
  }
}

Рассматривая декларацию abs, унаследованную от C (потому что вы включили stdlib.h)

int       abs( int n );
long      abs( long n );
long long abs( long long n ); //    (since C++11)
//Defined in header <cinttypes>
std::intmax_t abs( std::intmax_t n ); //    (since C++11)

и abs в C++ (из cmath)

float       abs( float arg );
double      abs( double arg );
long double abs( long double arg );

Если вы заметили, как аргумент, так и возвращаемый тип каждой функции signed. Поэтому, если вы передадите тип без знака одной из этих функций, произойдет неявное преобразование unsigned T1 -> signed T2 -> unsigned T1 (где T1 и T2 могут быть одинаковыми, а T1 в вашем случае - long). Когда вы конвертируете неподписанный интеграл в подписанный интеграл, поведение зависит от реализации, если его нельзя представить в подписанном типе.

Из 4.7 Интегральных преобразований [conv.integral]

  1. Если тип назначения не имеет знака, полученное значение является наименьшим целое число без знака, совпадающее с целым числом источника (по модулю 2 n, где n количество битов, используемых для представления типа без знака). [Примечание: в дополняют представление, это преобразование является концептуальным и нет изменений в битовой комбинации (если нет усечения). - конечная нота]
  2. Если тип назначения подписан, значение не изменяется, если оно может быть представлены в типе назначения (и ширине битового поля); в противном случае, значение определяется реализацией.

Ответ 2

Я не знаю, считаете ли вы его смысл, но abs(), примененный к значению без знака, может, конечно, вернуть значение, отличное от того, которое было передано. Это потому, что abs() принимает аргумент int и возвращает значение int.

Например:

#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    unsigned u1 = 0x98765432;
    printf("u1 = 0x%.8X; abs(u1) = 0x%.8X\n", u1, abs(u1));
    unsigned long u2 = 0x9876543201234567UL;
    printf("u2 = 0x%.16lX; abs(u2) = 0x%.16lX\n", u2, labs(u2));
    return 0;
}

При компиляции C или С++ (с использованием GCC 4.9.1 в Mac OS X 10.10.1 Yosemite), он производит:

u1 = 0x98765432; abs(u1) = 0x6789ABCE
u2 = 0x9876543201234567; abs(u2) = 0x6789ABCDFEDCBA99

Если высокий бит значения без знака установлен, тогда результат abs() не является значением, переданным функции.

Вычитание - это просто отвлечение; если результат имеет самый старший бит, значение, возвращаемое из abs(), будет отличаться от значения, переданного ему.


Когда вы компилируете этот код с заголовками С++, вместо заголовков C, показанных в вопросе, он не может скомпилировать с неоднозначными ошибками вызова:

#include <cstdlib>
#include <iostream>
using namespace std;

int main(void)
{
    unsigned u1 = 0x98765432;
    cout << "u1 = 0x" << hex << u1 << "; abs(u1) = 0x" << hex << abs(u1) << "\n";
    unsigned long u2 = 0x9876543201234567UL;
    cout << "u2 = 0x" << hex << u2 << "; abs(u2) = 0x" << hex << abs(u2) << "\n";
    return 0;
}

Ошибки компиляции:

absuns2.cpp: In function ‘int main()’:
absuns2.cpp:8:72: error: call of overloaded ‘abs(unsigned int&)’ is ambiguous
     cout << "u1 = 0x" << hex << u1 << "; abs(u1) = 0x" << hex << abs(u1) << "\n";
                                                                        ^
absuns2.cpp:8:72: note: candidates are:
In file included from /usr/gcc/v4.9.1/include/c++/4.9.1/cstdlib:72:0,
                 from absuns2.cpp:1:
/usr/include/stdlib.h:129:6: note: int abs(int)
 int  abs(int) __pure2;
      ^
In file included from absuns2.cpp:1:0:
/usr/gcc/v4.9.1/include/c++/4.9.1/cstdlib:174:3: note: long long int std::abs(long long int)
   abs(long long __x) { return __builtin_llabs (__x); }
   ^
/usr/gcc/v4.9.1/include/c++/4.9.1/cstdlib:166:3: note: long int std::abs(long int)
   abs(long __i) { return __builtin_labs(__i); }
   ^
absuns2.cpp:10:72: error: call of overloaded ‘abs(long unsigned int&)’ is ambiguous
     cout << "u2 = 0x" << hex << u2 << "; abs(u2) = 0x" << hex << abs(u2) << "\n";
                                                                        ^
absuns2.cpp:10:72: note: candidates are:
In file included from /usr/gcc/v4.9.1/include/c++/4.9.1/cstdlib:72:0,
                 from absuns2.cpp:1:
/usr/include/stdlib.h:129:6: note: int abs(int)
 int  abs(int) __pure2;
      ^
In file included from absuns2.cpp:1:0:
/usr/gcc/v4.9.1/include/c++/4.9.1/cstdlib:174:3: note: long long int std::abs(long long int)
   abs(long long __x) { return __builtin_llabs (__x); }
   ^
/usr/gcc/v4.9.1/include/c++/4.9.1/cstdlib:166:3: note: long int std::abs(long int)
   abs(long __i) { return __builtin_labs(__i); }
   ^

Итак, код в вопросе компилируется только тогда, когда используются только заголовки C-стиля; он не компилируется, когда используются заголовки С++. Если вы добавите <stdlib.h>, а также <cstdlib>, добавлена ​​дополнительная перегрузка, чтобы сделать вызовы более неоднозначными.

Вы можете сделать компиляцию кода, если вы добавите (в) соответствующие приведения к вызовам abs(), а абсолютное значение подписанной суммы может отличаться от оригинальной подписанной величины, что вряд ли удивительно новость:

#include <cstdlib>
#include <iostream>
using namespace std;

int main(void)
{
    unsigned u1 = 0x98765432;
    cout << "u1 = 0x" << hex << u1 << "; abs(u1) = 0x" << hex << abs(static_cast<int>(u1)) << "\n";
    unsigned long u2 = 0x9876543201234567UL;
    cout << "u2 = 0x" << hex << u2 << "; abs(u2) = 0x" << hex << abs(static_cast<long>(u2)) << "\n";
    return 0;
}

Вывод:

u1 = 0x98765432; abs(u1) = 0x6789ABCE
u2 = 0x9876543201234567; abs(u2) = 0x6789ABCDFEDCBA99

Мораль: не используйте заголовки C, для которых существуют С++-эквиваленты в коде на С++; вместо этого используйте заголовки С++.

Ответ 3

Я думаю, что когда c = a-b отрицательно, если c - непознанное число, c не является точным ответом. Использование abs для обеспечения c является положительным числом.