2016-10-10 3 views
2

У меня есть Arduino, который получает сообщение от моего смартфона (через bluetooth), содержащего временную метку unix. Теперь, я пытаюсь синхронизировать мой DS1307 с этой меткой времени.Странное поведение с atof

Однако это не сработало, поэтому я начал поиск и обнаружил странное поведение при преобразовании массива C-стиля, содержащего метку времени, в число с плавающей точкой.

// Copy the time into "timeBuff" 
int timeLength = siz - _CAT_LENGTH_; 
char timeBuff[timeLength+1]; 
memcpy(timeBuff, &msg[_CAT_LENGTH_], timeLength); 
timeBuff[timeLength] = '\0'; 

// For debugging 
Serial.print(F("timeBuff: ")); 
Serial.println(timeBuff); 

// Convert timeBuff string into a variable of type long 
float deviceTime = atof(timeBuff); 

// Now, deviceTime != what we printed above 
Serial.print(F("Epoch time: ")); 
Serial.println(deviceTime); 

Первые 5 строк копируют правую часть сообщения в массив символов и добавляет нулевой конечный результат.

Впоследствии мы печатаем содержимое timeBuff и преобразуем его в float, который хранится в deviceTime. Наконец, мы печатаем deviceTime.

Это был результат моего первого теста

timeBuff: 1476113620 
Epoch time: 1476113664.00 

И это второй тест

timeBuff: 1476115510 
Epoch time: 1476115456.00 

Почему результат atof отличается от строки мы прошли это?

+5

'float' не имеет достаточной точности, чтобы точно представлять значение. – Transcendental

+4

Почему вы имеете дело с 'float' здесь? –

+0

@MichaelWalz Мне нужно синхронизировать мой DS1307 (часы RTC для Arduino), поэтому мне нужно создать 'DateTime', который я передаю методу' adjust' библиотеки. Чтобы создать это 'DateTime', я хотел передать временную метку unix, полученную в сообщении. – HyperZ

ответ

5

На большинстве платформ float представлен в формате одноточечной обработки IEEE-754, который представляет собой 32-битный формат с плавающей запятой с 24-битным значением (23 + 1). Это просто не имеет достаточной точности для представления ваших чисел. Для вашего номера требуется около 32 бит. Целочисленные значения, превышающие 24 бита, обычно теряют точность при хранении в float.

Потеря точности будет проявляться в виде потери завершающих бит с округлением в последнем оставшемся бит

1476113620 -> ‭01010111111110111011010011010100‬ 
1476113664 -> ‭01010111111110111011010100000000‬ 
+0

Подробно: «Целочисленные значения шириной более 23 бит» -> выключено на 1. Тип одноточечного ввода IEEE-754 может точно представлять все положительные/отрицательные 24-битные целочисленные значения. IOWs все 'int25_t' и' uint24_t'. – chux

+0

Спасибо за разъяснение. Тем не менее, я немного смущен, почему они упоминают «float (32 бит) - количество подписанных от -3.4028235E38 до 3.4028235E38' на https://learn.sparkfun.com/tutorials/data-types-in-arduino – HyperZ

+0

@AnT Хорошо, спасибо за вашу помощь! Мне нужно будет прочитать еще немного о плавающих точках, которые я думаю :) – HyperZ

2

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

#include <stdio.h> 

int main() 
{ 
    float f = 1234567890.; 
    double d = 1234567890.; 
    printf("f = %f\n", f); 
    printf("d = %f\n", d); 
    return 0; 
} 

Я призываю вас собрать эту небольшую программу и запустить ее. Когда я это делаю, я вижу:

f = 1234567936.000000 
d = 1234567890.000000 

Сначала это кажется невозможным. Похоже, что это должно быть ошибкой в ​​компиляторе, или в printf, или что-то в этом роде. Если я присвою простое число «1234567890» переменной f, как он не может распечатать этот путь? И ответ: , потому что тип float не имеет достаточной точности.

Все понимают, что ограниченная точность означает, что вы не сможете точно представить число, например, 1.234567890, чтобы оно могло искаться в 1.234567936 или что-то в этом роде. И главное, чтобы узнать здесь, что любой тип с плавающей точкой (floatилиdouble или что-то еще) имеет ограниченное количество цифр значимости, периода. Это важно не только цифрами точности справа от десятичной точки. Для больших чисел вы даже не можете точно представлять все цифры слева от десятичной точки.(Фактически, этот момент - то, что вы имеете ограниченную точность, которая применяется по обе стороны от десятичной точки, - это то, что слово «плавающий» в «с плавающей запятой» фактически означает.)

+0

Одно замечание: поскольку вопрос касается Arduino, результаты будут одинаковыми для 'float' * и * для' double'. В Arduino 'sizeof (float) == sizeof (double) == 4' (по крайней мере по умолчанию: есть опция иметь 64-разрядные' double ', но они будут еще медленнее, чем' float'). –

+0

@ TimČas О, мой. Благодарю. (Интересно, что это даже соответствует стандарту C? Моя неопределенная память состоит в том, что для «двойного» требуется эквивалент не менее десяти десятичных цифр.) –

+0

Хм ... возможно. 'DECIMAL_DIG' указан в терминах * самого широкого * поддерживаемого типа (я предполагаю, что по крайней мере' long double' там 64-бит, так что * будет * соответствовать) ... но 'DBL_DIG <10' нарушает как C89 (2.4 .2.2; неизвестно) * и * C99 (5.2.4.2.2p9)! Тем не менее, Arduino - это C++, а не C, поэтому * может быть * все по-другому (я сомневаюсь, потому что C++ 98 основан на C89, но все же)? В любом случае, если он не соответствует требованиям, это будет означать, что в то время как один * не должен был * беспокоиться о «двойном», не имея достаточной точности, в этом случае все еще должно быть на практике. –

Смежные вопросы