Дополнение до двух и потеря информации в C

Я хочу сделать два дополнения к данным с плавающей запятой.

    unsigned long Temperature ; 
    Temperature = (~(unsigned long)(564.48))+1;

Но проблема в том, что приведение теряет информацию, 564 вместо 564,48. Могу ли я сделать два дополнения без потери информации?


person physics    schedule 02.09.2014    source источник
comment
564.48 это double, приведение к unsigned long урежет его до 564. Что вы ожидаете?   -  person Yu Hao    schedule 02.09.2014
comment
плавающее значение memcpy в unsigned long, а затем применить компонент two. Или используйте объединение с float и unsigned long, назначьте 564.48f для float и примените компонент two к unsigned long.   -  person Alex F    schedule 02.09.2014
comment
@AlexFarber: все они будут демонстрировать UB.   -  person bitmask    schedule 02.09.2014
comment
Что вам на самом деле нужно сделать? Выполнение дополнения до двух для данных с плавающей запятой не имеет смысла, оно никоим образом не будет полезным.   -  person interjay    schedule 02.09.2014
comment
знаете ли вы, что нет шкалы температуры, где -564,48 является допустимым значением?   -  person mch    schedule 02.09.2014
comment
Да, это действительное значение. Потому что я делаю это после смены знака. Назначение двух дополнений состоит в том, чтобы передать это значение через Arinc.   -  person physics    schedule 02.09.2014
comment
@mch ~ отличается от -   -  person M.M    schedule 02.09.2014
comment
@bitmask: предполагая, что OP точно знает, что он делает, и ему нужно применить побитовую операцию к веществу с плавающей запятой, как и к целому числу, это то, что ему нужно сделать. Похоже, это ему нужно для общения, в этом есть смысл.   -  person Alex F    schedule 02.09.2014
comment
@bitmask: каламбур типов через memcpy и union четко определен (за исключением представлений-ловушек); чтение от члена профсоюза, которому вы не писали, было ошибочно указано как неопределенное поведение в (ненормативном) приложении к C99; это было исправлено с помощью C11   -  person Christoph    schedule 02.09.2014
comment
@Кристоф: Интересно. Не знал этого. По-видимому, это еще одно различие между C и C++. Спасибо за информацию!   -  person bitmask    schedule 02.09.2014


Ответы (3)


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

В любом случае, вы можете использовать старый добрый трюк union:

union {
  float real;
  unsigned long integer;
} tmp = { 564.48 };

tmp.integer = ~tmp.integer + 1;
printf("I got %f\n", tmp.real);

Когда я попробовал это (на идеоне), он напечатал:

I got -0.007412

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

person unwind    schedule 02.09.2014
comment
Не знаете, откуда вы взяли it's safe in modern compilers? - person tangrs; 02.09.2014
comment
@tangrs Я тоже ... Я переписал это, чтобы было понятнее. - person unwind; 02.09.2014
comment
Может быть, я ошибаюсь, но кажется, что ОП хочет (~tmp.integer) + 1 вместо ~tmp.integer + 1 - person David Ranieri; 02.09.2014
comment
@AlterMann Эти выражения абсолютно эквивалентны, ~ имеет более высокий приоритет, чем + . - person unwind; 02.09.2014
comment
@unwind, не в этом случае, tmp.integer = ~tmp.integer; и tmp.integer = ~tmp.integer + 1; дают один и тот же результат: ideone.com/siXo94, результат должно быть 0.992588, если вы добавляете 1 после преобразования - person David Ranieri; 02.09.2014
comment
@AlterMann Что? Что 1 добавляется в целочисленное выражение, оно не может преобразовать -0.007412 в 0.992588? Это просто добавление очень незначительного бита к дробной части числа с плавающей запятой (при условии того же порядка битов для чисел с плавающей запятой, что и для целых чисел). Вместо этого я попытался добавить 10000, что меняет значение float на -0.007417. - person unwind; 02.09.2014
comment
Я имею в виду ~564.48 = -0.007412 (ваш результат), а ОП хочет -0.007412 + 1 (0.992588), чтобы было понятнее, вы не вычисляете +1: посмотрите: ideone.com/gnjMDx - person David Ranieri; 02.09.2014
comment
почему бы просто не использовать tmp.integer = -tmp.integer? - person phuclv; 02.09.2014
comment
@LưuVĩnhPhúc Потому что для этого потребуется, чтобы машина использовала 2-секундное дополнение, что не гарантируется. - person unwind; 02.09.2014
comment
Я не думаю, что машина ОП не использует дополнение 2, за исключением случаев, когда он использует какую-то древнюю машину. - person phuclv; 02.09.2014

Вы не можете использовать ~ над числами с плавающей запятой (это должен быть целочисленный тип):

#include <stdio.h>

void print_binary(size_t const size, void const * const ptr)
{
    unsigned char *b = (unsigned char *) ptr;
    unsigned char byte;
    int i, j;

    for (i = size - 1; i >= 0; i--) {
        for (j = 7; j >= 0; j--) {
            byte = b[i] & (1 << j);
            byte >>= j;
            printf("%u", byte);
        }
    }
    printf("\n");
}

int main(void)
{
    float f = 564.48f;
    char *p = (char *)&f;
    size_t i;

    print_binary(sizeof(f), &f);
    for (i = 0; i < sizeof(float); i++) {
        p[i] = ~p[i];
    }
    print_binary(sizeof(f), &f);
    f += 1.f;
    return 0;
}

Выход:

01000100000011010001111010111000
10111011111100101110000101000111

Конечно, print_binary предназначен для проверки результата, удалите его, и (как указал @barakmanos) print_binary предполагает прямой порядок байтов, остальная часть кода не зависит от порядков байтов:

#include <stdio.h>

int main(void)
{
    float f = 564.48f;
    char *p = (char *)&f;
    size_t i;

    for (i = 0; i < sizeof(float); i++) {
        p[i] = ~p[i];
    }
    f += 1.f;
    return 0;
}
person David Ranieri    schedule 02.09.2014
comment
Дает разные результаты для прямого и прямого порядка байтов. - person barak manos; 02.09.2014
comment
@barakmanos, ты уверен? Endianess влияет только на порядок байтов. Поскольку char — это ровно один байт, он одинаков для всех форматов порядка следования байтов. Посмотрите - person David Ranieri; 02.09.2014
comment
Да, но вы преобразуете float* в char*, когда вызываете print_binary. - person barak manos; 02.09.2014
comment
Кстати, я не сказал, что это неправильно, я имел в виду, что это стоит упомянуть как часть ответа... - person barak manos; 02.09.2014
comment
@barakmanos, а, хорошо, вы говорите о части print_binary, вы правы, предполагает прямой порядок байтов. - person David Ranieri; 02.09.2014
comment
разве вы не должны использовать unsigned char также в main вместо char? если char подписано, это не является исключением из строгого правила алиасинга. - person mch; 02.09.2014
comment
Спасибо... На минуту я подумал, что цикл for (где вы фактически меняете значение с плавающей запятой) также подвергается порядку байтов лежащей в основе аппаратной архитектуры, но потом я понял, что вы делаете это для каждого байта отдельно. - person barak manos; 02.09.2014
comment
@mch, подписанность не имеет значения для правил псевдонимов, вы можете использовать как char *, так и unsigned char * - person David Ranieri; 02.09.2014

Приведение значения с плавающей запятой к целочисленному значению изменяет «битовое содержимое» этого значения.

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

float f = 564.48f;
unsigned long Temperature = ~*(unsigned long*)&f+1;

Убедитесь, что sizeof(long) == sizeof(float), или используйте double вместо float.

person barak manos    schedule 02.09.2014
comment
Выполнение дополнения до двух к битовому содержимому числа с плавающей запятой — совершенно бессмысленная операция. - person interjay; 02.09.2014
comment
@interjay: Это стоит упомянуть. Я думал об этом, но подумал, что у ОП могут быть свои причины (возможно, нужно записать это в регистр или сохранить в какой-то двоичной базе данных и т. Д.). - person barak manos; 02.09.2014
comment
в C++ это UB из-за строгого правила алиасинга. Я не уверен насчет C, я думаю, что в C все в порядке, вы можете подтвердить? - person bolov; 02.09.2014
comment
@bolov Это может сломаться и на C. Вместо этого лучше использовать memcpy. - person user694733; 02.09.2014
comment
@bolov: я не совсем уверен, как это подтвердить. Я ожидаю, что если оба типа имеют одинаковый размер, то ничто не помешает успешному выполнению этого, то есть без ошибки сегментации и с детерминированным результатом (конечно, отличающимся на LE и BE, как я упоминается в конце ответа). - person barak manos; 02.09.2014
comment
@ user694733: Это из-за выравнивания адреса float? - person barak manos; 02.09.2014
comment
@barakmanos Возможно. Если sizof оба типа одинаковы, они обычно имеют одинаковое выравнивание. Но никаких гарантий нет, и уже одно это является причиной не использовать его. - person user694733; 02.09.2014