Контрольная сумма с плавающей точкой с текстовым файлом

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

Я адаптировал этот алгоритм CRC32 которую я нашел после прочтения этот вопрос. Вот как это выглядит:

uint32_t updC32(uint32_t octet, uint32_t crc) {
return CRC32Tab[(crc ^ octet) & 0xFF] ^ (crc >> 8);
}

template <typename T>
uint32_t updateCRC32(T s, uint32_t crc) {
const char* buf = reinterpret_cast<const char*>(&s);
size_t len = sizeof(T);

for (; len; --len, ++buf)
crc = updC32(static_cast<uint32_t>(*buf), crc);
return crc;
}

CRC32Tab содержит те же значения, что и большой массив в файле, указанном выше.

Это сокращенная версия того, как я записываю числа с плавающей точкой в ​​файл и вычисляю контрольную сумму:

float x, y, z;

// set them to some values

uint32_t crc = 0xFFFFFFFF;
crc = Utility::updateCRC32(x, crc);
crc = Utility::updateCRC32(y, crc);
crc = Utility::updateCRC32(z, crc);
const uint32_t actualCrc = ~crc;

// stream is a FILE pointer, and I don't mind the scientific representation
fprintf(stream, " ( %g %g %g )", x, y, z);
fprintf(stream, " CRC %u\n", actualCrc);

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

float x = std::atof(getNextFloat());
float y = std::atof(getNextFloat());
float z = std::atof(getNextFloat());

uint32_t crc = 0xFFFFFFFF;
crc = Utility::updateCRC32(x, crc);
crc = Utility::updateCRC32(y, crc);
crc = Utility::updateCRC32(z, crc);
const uint32_t actualCrc = ~crc;

const uint32_t fileCrc = // read the CRC from the file
assert(fileCrc == actualCrc); // fails often, but not always

Источником этой проблемы является то, что std :: atof будет возвращать другое битовое представление с плавающей запятой, закодированное в строке, которая была прочитана из файла, чем битовое представление с плавающей запятой, которое использовалось для записи этой строки в файл.

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

Спасибо за прочтение!

1

Решение

Источник проблемы очевиден из вашего комментария:

Если я не совсем ошибаюсь, здесь не происходит округления. %g Спецификатор выбирает самое короткое строковое представление, которое точно представляет число.

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

Если вам нужен читаемый человеком круглый формат, %a безусловно лучший выбор. В противном случае вам нужно будет указать точность не менее 9 (при условии, что float в вашей системе есть IEEE-754 одинарной точности).

Вы все равно можете быть сбиты с толку NaN-кодировками, поскольку в стандарте не указано, как или нужно их печатать.

1

Другие решения

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

1

Если преобразования стандартной и обычной текстовой библиотеки в вашей стандартной библиотеке выполняют правильное округление, вам просто нужно достаточно значащих цифр, чтобы обход с плавающей запятой -> text-> float был без потерь, если у вас нет также Infs и NaNs, но все равно следует быть «сохраняющим значение», не обязательно сохраняющим битовый шаблон, так как я думаю, что существует множество представлений для бесконечности или NaN. Для IEEE-754 64-битных двойных 17 значащих цифр вполне достаточно, чтобы сделать обход без потерь по отношению к фактическому значению.

0

Ваш алгоритм CRC имеет недостатки для любого типа, который имеет несколько двоичных представлений для одного значения. IEEE 754 имеет два представления: 0,0, то есть +0,0 и -0,0. Другие, не ограниченные значения, такие как NaN, также потенциально проблематичны.

0

Будет ли приемлемо канонизировать ваши номера перед обновлением CRC? Поэтому при сохранении вы получите временную строковую версию своего номера (с sprintf или любым другим, соответствующим формату вашей сериализации), затем преобразуете эту строку обратно в числовое значение и затем используйте этот результат для обновления CRC. Таким образом, вы знаете, что CRC будет соответствовать десериализованному значению.

0
По вопросам рекламы [email protected]