2015-06-27 2 views
1

Я пишу функцию, которая, предположительно, должна иметь поплавковую длину, а затем отображать выходные данные таймера. Длина «поплавка» должна составлять минуты.Значение поплавка не сохраняется правильно

Когда я запускаю его, выход должен быть: 02: 03: 27: 00. Вместо этого он отображает 02: 03: 26: 100, будучи технически корректным, это A) Не так, как это должно отображаться, и B) Показывает, что есть ошибка, которая может привести к нежелательным результатам в будущем.

Пройдя через него вручную с калькулятором и найдя все математические данные для звука. Затем я прокомментировал части, которые форматируют нули, чтобы увидеть, вызвало ли это ошибку, и проблема все еще была там. Затем я прошел через печать printf после каждого расчета и обнаружил, что, когда «length» установлен в 123.45, он хранится как 123.449997?

У меня нет подсказки, как это делается. И поскольку я не знаю, как это происходит, я не могу написать надежное решение.

int main() 
{ 

float length; 
float working; 
int hour; 
int min; 
int sec; 
int centi_sec; 
char hzero[2]; 
char mzero[2]; 
char szero[2]; 
char czero[2]; 

    length=123.45; 
    working=floor(length); 
    working=(length-working)*60; 
    sec=floor(working); 
    working-=floor(working); 
    centi_sec=(working*100)+.5; 
    working=floor(length); 
    hour=floor((working/60)); 
    min=working-(hour*60); 
    if(hour<10){ 
     hzero[0]='0'; 
     hzero[1]=""; 
    } 
    else if(hour==0){ 
     hzero[0]='0'; 
     hzero[1]='0'; 
    } 
    else{ 
     hzero[0]=""; 
     hzero[1]=""; 
    } 
    if(min<10){ 
     mzero[0]='0'; 
     mzero[1]=""; 
    } 
    else if(min==0){ 
     mzero[0]='0'; 
     mzero[1]='0'; 
    } 
    else{ 
     mzero[0]=""; 
     mzero[1]=""; 
    } 
    if(sec<10){ 
     szero[0]='0'; 
     szero[1]=""; 
    } 
    else if(sec==0){ 
     szero[0]='0'; 
     szero[1]='0'; 
    } 
    else{ 
     szero[0]=""; 
     szero[1]=""; 
    } 
    if(centi_sec<10){ 
     czero[0]='0'; 
     czero[1]=""; 
    } 
    else if(centi_sec==0){ 
     czero[0]='0'; 
     czero[1]='0'; 
    } 
    else{ 
     czero[0]=""; 
     czero[1]=""; 
    } 
    printf("%s%d:%s%d:%s%d:%s%d\n", hzero, hour, mzero, min, szero, sec, czero, centi_sec); 
    system("pause"); 

} 

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

int main() 
{ 

float length=123.45; 

    printf("%f\n", length); 
    system("pause"); 

} 

P.S. Когда я использовал printf для устранения этой проблемы, я обнаружил, что printf испортили форматирование нулей. Не слишком большая проблема, поскольку, когда я удалил их, форматирование вернулось к тому, как это должно быть. Тем не менее, не имеет никакого смысла, что printf будет испортить форматирование. Если кто-то может дать ответ на это, это будет оценено.

Заранее спасибо.

+0

У вас есть потенциальная проблема, потому что 'floor()' не возвращает 'int', он возвращает' double'. Кроме того, это не '' hzero [0] = ""; "генерировать предупреждения компилятора? –

+0

У вас есть большой не научный тип данных для использования – Drew

ответ

1

Вы присвоили двоичную с плавающей запятой значение с десятичным реальным значением. Двоичная с плавающей запятой не может представлять точно все действительные десятичные значения.

Одноточечная двоичная с плавающей запятой хороша для точного представления приблизительно 6 значащих десятичных цифр, а 123.449997 - 9 фигур; так что вы превысили обещанную точность. По умолчанию спецификатор формата% f отображает 6 знаков после запятой, но в этом случае это превышает допустимую точность.

Либо использовать спецификатор формата, который отображает до разумной точности:

printf("%.3f\n", length) ; 

или использовать double который хорош для 15 десятичных значащих цифр.

Существует несколько причин не использовать двойную точность для цели с высокой пропускной способностью памяти и аппаратной единицей с плавающей запятой (т. Е. Все современные настольные компьютеры). Одиночная точность полезна, если вы обрабатываете действительно огромные объемы данных и должны сократить время обработки и не нуждаетесь в точности.

+0

Спасибо, он работает так, как сейчас. – Ulrick

0

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

printf("%.2f\n", 123.449997); 

123.45 будет напечатаны, и также для любой арифметической операции с участием значения, результат будет правильным для достаточно десятичных знаков точности, 2.

Самая важная проблема, которая у вас есть: что ваши строки не могут быть такими строк, потому что нет места для завершения '\0'.

И математика тоже неправильно, потому что если centi_sec больше или равно 100 то следует добавить один второй и 100 следует вычесть из centi_sec, и то же самое относится и к sec с min, и так далее.

Эти

char hzero[2]; 
char mzero[2]; 
char szero[2]; 
char czero[2]; 

следует читать

char hzero[3]; 
char mzero[3]; 
char szero[3]; 
char czero[3]; 

Вы также не должны повторяться, используйте функцию

#include <math.h> 
#include <stdio.h> 

void zeropad(int value, char str[3]) 
{ 
    if (value < 10) 
    { 
     str[0] = '0'; 
     str[1] = value + '0'; 
    } 
    else 
    { 
     str[0] = (value - value % 10)/10 + '0'; 
     str[1] = value % 10 + '0'; 
    } 
    str[2] = '\0'; 
} 

int main() 
{ 
    float length; 
    float working; 
    int hour; 
    int min; 
    int sec; 
    int centi_sec; 
    char hzero[3]; 
    char mzero[3]; 
    char szero[3]; 
    char czero[3]; 

    length = 123.45; 
    working = floor(length); 
    working = (length - working) * 60; 
    sec  = floor(working); 
    working -= floor(working); 
    centi_sec = (working * 100) + .5; 
    working = floor(length); 
    hour  = floor(working/60); 
    min  = working - (hour * 60); 

    if (centi_sec >= 100) 
    { 
     sec  += 1; 
     centi_sec -= 100; 
    } 

    if (sec >= 60) 
    { 
     min += 1; 
     sec -= 60; 
    } 

    if (min >= 60) 
    { 
     hour += 1; 
     min -= 60; 
    } 

    zeropad(hour, hzero); 
    zeropad(min, mzero); 
    zeropad(sec, szero); 
    zeropad(centi_sec, czero); 

    printf("%s:%s:%s:%s\n", hzero, mzero, szero, czero); 
} 
+1

. Это может быть проблема с отправленным кодом, но он не имеет отношения к заданному вопросу и должен быть опубликован как комментарий, если вообще. – Clifford

+0

Проблема с длиной также присутствует в вашем примере, но void zeropad устраняет проблему с испортить форматирование printf. Я до сих пор знаю, что c, и я не совсем понимаю все, что происходит в zeropad. Я получаю некоторые из них, но не все. Кроме того, я до сих пор не могу понять, почему printf будет испортить форматирование в первую очередь? – Ulrick

0

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

Мое первое предложение пройти небольшую структуру, которая содержит 4 целых полей

int hours 
int minutes 
int seconds 
int fractionSecondX100 

Таким образом, чтобы избежать всей проблемы установки с поплавками.

Однако, если вы должны использовать значение с плавающей точкой, то Google: «как обрабатывать значения с плавающей точкой в ​​C», это вернет много «хитов» следуя за Google, прочитал несколько ссылочных веб-страниц, так что вы знаете, как обрабатывать поплавки и знать, чего ожидать.

1

Бинарное представление десятичной дроби не может быть точной во всех случаях по математическим причинам. Вы можете узнать больше об этом here

Чтобы избежать проблем, вам нужно добавить половину вашего наименьшего устройства на вход. В этом случае это будет 1.0/60/100/2.

length = 123.45; 
const float epsilon = 1.0/60/100/2; 
length += epsilon; 

Вы пытались сделать что-то подобное с

centi_sec=(working*100)+.5; 

, но это имеет эффект только на centi_sec, а не на другие номера. Замените его на

centi_sec=(working*100); 

Пожалуйста, также измените размеры массива, как предлагается @iharob.

Edit: Вы можете избежать массивов в целом:

#include <math.h> 
#include <stdio.h> 

int main() 
{ 

const float epsilon = 1.0/60/100/2; 
float length; 
float working; 
int hour; 
int min; 
int sec; 
int centi_sec; 

    length=123.45; 
    length += epsilon; 
    working=floor(length); 
    working=(length-working)*60; 
    sec=floor(working); 
    working-=floor(working); 
    centi_sec=(working*100); 
    working=floor(length); 
    hour=floor((working/60)); 
    min=working-(hour*60); 
    printf("%02d:%02d:%02d:%02d\n", hour, min, sec, centi_sec); 
// system("pause"); 

} 

Это работает.

+0

Это фактически делает его менее точным, чем раньше. – Ulrick

+0

Я сделал более простую версию вашей программы, она работает для меня. – alain

+0

Проблема заключалась в том, что float хранился как 123.449997, в вашем коде он хранится как 124.450081, что менее точно, чем мое. Но на самом деле это полностью устраняет необходимость в нулевых массивах. Так что это большое улучшение в этом аспекте. – Ulrick