2015-06-02 2 views
2

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

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

Наш наставник сумасшедший о всех средствах обеспечения ввода, и поэтому я должен вызвать ошибку при любом несоответствии. Слишком высокая стоимость? Ошибка. Отрицательное или нулевое значение? Ошибка. Число точнее 0,01? Ошибка. Символы, отличные от цифр и точек, на входе? Ошибка.

Из-за этого моя функция, безусловно, сложна, но я в порядке с этим. То, что подталкивает меня к стене, состоит в том, что функции atof() и strtod() читают цифры как-то неправильно.

long double take_amount() 
{ 
    char input[21]; 
    bool success, correct; 
    unsigned long int control1; 
    long double amount, control, control2, control3; 

    do 
    { 
     success = true, correct = true; 
     printf("\t\tAmount:\t\t"); 
     success = scanf("%20[^ \t\n]c", input); 
     __fpurge(stdin); 

     correct = check(input, 'b'); 

     amount = strtod(input, NULL); 

     printf("\n\tGOT %.20Lf\n", amount); /// 

     control = amount * 100; 
     control1 = (unsigned long int)floor(control); 
     control2 = (long double) control1; 
     control3 = control2 - control;  

     printf("\n\tGOT2 %.20Lf\n", control3); /// 

     if (control3 != 0) 
     { 
      if (control3 >= 1 || control3 <= -1) 
      { 
       printf("\n\t\tWe are sorry, but for the safety reasons it is impossible to transfer"); 
       printf("\n\t\tsuch a great amounts while online. If you really wish to finalize"); 
       printf("\n\t\tthis operation, please, visit the closest division of our bank."); 
       printf("\n\t\tWe are sory for the inconvenience and wish you a pleasent day."); 
       press_enter(); 

      } 
      correct = false; 
      printf("\nDAMN\n");  ///  

     } 

     if (amount <= 0) 
     { 
      correct = false; 
      printf("\nGOD DAMN\n");  /// 
     } 

     if(success == false || correct == false) 
     { 
      printf("\n\t\tInvalid input, please try again.\n\n"); 
     } 
     else 
     { 
      printf("\t\t\t\t%.2Lf\n", amount); 
      printf("\n\t\tIs it correct input? ((Y)es/(N)o/(E)xit)"); 
      if (accept()) 
      { 
       break; 
      } 
      else 
      { 
       continue; 
      } 
      break; 
     } 
    }while (1); 
    return amount; 

Насколько мои функции, используемые здесь идет, check() проверяет, является ли строка содержит только допустимые символы (цифры и точка), press_enter() ждет для ввода-пресс, чтобы оставить в главное меню и accept() читает только у/п/e, вернуть true по y, false на n и выходит в меню на e.

Длительная часть с контрольными переменными - это мое решение для проверки того, является ли число не точнее 0,01. К сожалению, это не работает из-за strtod().

Моя проблема в том, что strtod() на самом деле не работает! Даже с действительно номерами медикаментов, находящимися далеко от нижнего или переполнения, возвращаемое значение не соответствует введенному. Некоторые примеры:

 
    Enter the amount you would like to deposit. 
     Amount:  1234.45 

    GOT 1234.45000000000004547474 

    Enter the amount you would like to deposit. 
     Amount:  0.999 

    GOT 0.99899999999999999911 

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

Есть ли способ исправить показания strtod()? Если нет, есть ли другой способ взять этот ввод, который позволит мне проверить все, что нужно проверить?

EDIT: ВНИМАНИЕ, ПОЖАЛУЙСТА!

Если я еще не заявил, что мой наставник не самый простой парень, с которым я работаю, я делаю это сейчас.
Он ПРЕДЛАГАЕТ использовать формат DOUBLE для хранения БАЛАНСА, хранящегося на счетах. Я знаю это, потому что один из моих коллег получил свою программу REJECTED для использования двухтактной, долларовой конструкции.

Я очень благодарен вам за помощь здесь, так как вы можете как-то решить эту проблему? Я должен использовать двойной тип для хранения денег. Я также должен проверить, не было ли буквы на входе (scanf(), установленный на %Lf, будет просто вырезать все цифры без конца), ИСПЫТАТЬ, если ввод не точнее, чем 0,01, ИМЕЕТ принять структуру xxx.xx ввода. Какие-либо предложения?

+4

'strtod' и связанные с ним функции, а' double' сам по себе не может быть «исправлен». В основном потому, что с ними нет ничего плохого: [Что каждый программист должен знать о арифметике с плавающей точкой] (http://floating-point-gui.de/). Найдите решение, которое не использует тип с плавающей точкой. – usr2564301

+1

Добро пожаловать в мир чисел с плавающей точкой; из-за того, как они представлены внутри, существуют десятичные значения, которые невозможно точно представить. –

+0

Это характер арифметики с фиксированной точностью. Так же, как нет десятичного числа фиксированной длины, которое дает 1 при умножении на 3, нет двоичного числа фиксированной длины, которое дает 1 при умножении на 10. –

ответ

9

Используйте strtol, чтобы прочитать доллары и снова (или только свою собственную тривиальную функцию), чтобы читать центы, а затем объединить их как cents += 100*dollars; Не используйте с плавающей точкой для валюты. Когда-либо.

+0

Прежде всего, спасибо за ответ. Я бы действительно ЛЮБЛЮ, но, опять же, репетитор, который отмечает наши программы, часто может сходить с ума, если наши программы не работают, как он себе представлял. Если он хочет ввести оба доллара и центов сразу, как в 10.99, мы должны заставить его работать. Предположим, что я бы использовал strtol, возьму непринятую часть строки, тогда мне пришлось бы ввести дополнительную функцию, которая может пропустить точку (но только точку), прочитать снова ... И что еще хуже, измените больше или меньше половины кода, поэтому он использует int не в два раза, чтобы удерживать баланс ... Ну, я думаю, это лучшее, что я могу сделать сейчас. :/ – ryfterek

+0

Нет проблем с 10.99. Первый 'strtol' читает целое число 10. Второй читает целое число 99. Тогда 99 + 10 * 100 дает вам 1099 центов. –

+0

Проблема заключается в том, что предполагаемый мой freaking tutor faceroll 123.gtrv246vbf1fs или 0.2.023tdfq или - [iu76tgbr56, программа должна реагировать должным образом (то есть возвращать ошибку, связанную с неправильным вводом). Более того, как указано в редакции к исходному сообщению, он заставляет нас использовать двойной тип для баланса счета, поэтому подход, основанный только на центах, не может быть и речи. – ryfterek

2

Номера с плавающей точкой IEEE предназначены для хранения приблизительных значений в огромном диапазоне, а не в точном значении.Они не могут точно представлять некоторые очень простые значения, например 0.1, но они могут представлять 1.0, 0.5, 0.25, 0.125, ... точно. Они также могут представлять 2.0, 4.0, 8.0, 16.0, ... точно. Как далеко эти серии идут, связано с тем, сколько бит представляет значение с плавающей запятой. Вы должны были заметить, что все приведенные мной значения параметров имеют степень 2, и из этого вы должны увидеть, что значения с плавающей запятой также могут состоять из сумм степеней 2, что в основном правильное. Суммирование значений 2 хорошо работает для представления целых чисел, так как весь диапазон от наименьшего до наибольшего может быть представлен целыми типами, но с дробными значениями разные вещи. Существует множество значений между значениями, которые могут быть представлены, которые не могут быть выражены для данного типа с плавающей точкой (32, 64, 80 или 128 бит с плавающей запятой), поэтому, когда они считываются функциями библиотеки C (или компилятор из вашего исходного кода) есть правила округления, которые вступают в игру. Очень похоже на то, что цифры записываются программой C.

Когда вам нужно иметь дело с точными значениями, вы должны знать об этом. Для денег, которым обычно нужен такой тип точности, вы захотите преобразовать его в тип, который может представлять этот тип точности. На многих языках для этого существуют отдельные типы, но в C вы можете поместить значения в целые типы или структуры, состоящие из целых чисел.

0

Это известная «проблема» в отношении того, как числа представлены в плавающей запятой. Представление 1234.45 в плавающей точке - это то, что вводится.

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

Другой вариант, который вы можете сделать, это создать структуру «Валюта», которая содержит два ints (часть доллара и дробная часть) и передает их. Вам все равно придется обрабатывать код для разделения строки валюты на эти части.

0

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

Этот факт заставляет меня задаться вопросом: считаете ли вы, что вы храните свои валютные ценности так, чтобы 1234,45 долларов США составляли 123445?

1

Разделяй и властвуй

Отдельный пользовательский ввод от проверки данных.вход


Пользователь:

Текущий код использует input без первого подтверждения success

success = scanf("%20[^ \t\n]c", input); 
correct = check(input, 'b'); 
amount = strtod(input, NULL); // Bad: `input` not known to have been successfully scanned. 

Вместо:

char input[40]; // no need for tight buffer size 
if (fgets(input, sizeof input, stdin) == NULL) Handle_EOF(); 
// remove potential trailing \n 
input[strcspn(input, "\n")] = 0; 

Теперь начать оценивать если ввод действителен при различных тестах

char *endptr; 
errno = 0; 
double value = strtod(input, &endptr); 
if (input == endptr) Handle_NothingScanned(); 
if (*endptr != '\0') Handle_GarbageAtEndOfInput(); 

// Use 1 of the 2: First allows input like 1e-1000 which underflows to 0.0 
if (errno == ERANGE && fabs(value) > DBL_TRUE_MIN) Handle_Overflow(); 
if (errno == ERANGE) Handle_OverUnderflow(); 

// work with numbers in a reasonable range of 'double' 
double max_range = pow(10.0, DBL_DIG - 2); 
if (!(value >= -max_range && value <= max_range)) Handle_InfinityNotanumberOverflow(); 

// Test insures that the number entered is the closest possible `double` to xxxxxx.xx 
// Method 1: Math 
double rvalue = round(value*100.0)/100.0; 
if (rvalue != value) Handle_NumberMorePreciseThan_0_01(); 
// Method 2: round trip: double --> string --> double 
char buffer[DBL_DIG + 10]; 
sprintf(buffer, "%.2f", value); 
if (value != strtod(buffer, 0)) Handle_NumberMorePreciseThan_0_01(); 

// Insure xxxx.xx format 
if ((endptr - input) < 3 || endptr[-3] != '.') Handle_BadFormat(); 

GoodToUse(value); 
Смежные вопросы