【问题标题】:How to stop scanf from parsing the integral part when the input is a float当输入为浮点数时如何停止scanf解析整数部分
【发布时间】:2021-06-07 18:23:51
【问题描述】:

所以这个程序是一个简单的十进制到二进制转换器。

我希望我的代码重复,直到用户按下 ctrl + D。我还想让用户知道,如果他们输入像-21.1 这样的数字,输入的数字不是正整数。

问题是当他们输入float 时,我的代码只会无限打印我的第一个打印语句。

这是我的代码:

void DecToBin(int userInput){
   int binary[32];
   int i = 0;
   while (userInput > 0) {
       binary[i] = userInput % 2;
       userInput /= 2;
       i++;
   }

   for (int j = i - 1; j >= 0; --j) {
       printf("%d", binary[j]);
   }
}
int main(void) {
    int userDec;
    int res;
    
    while(1) {
        printf("Please enter a positive whole number (or EOF to quit): ");
        res = scanf("%d", &userDec);
        res = (int)(res);
        if (res == EOF) break;
        else {
            if (!((userDec > 0) && (userDec % 1 == 0))) {
                printf("Sorry, that was not a positive whole number.\n");
            }
            else {
                printf("%d (base-10) is equivalent to ", res);
                DecToBin(userDec);
                printf(" (base-2)!");
                printf("\n");
            }
        }
    }
    printf("\n");
    
    return 0; 
}   

所以当我输入类似11.1 或负值时,它不应该接受它并在终端中打印"Please enter a positive whole number (or EOF to quit)"

应该接受像11.0 这样的输入。

所以,我要问的是我的代码有什么问题导致它这样做,我应该如何解决它?我应该改用float 吗?

【问题讨论】:

  • userDec % 1 == 0 应该测试什么条件?当然除以1总是会留下0的余数。
  • 您的 scanf 函数期望得到一个整数,而不是浮点型或双精度型。我假设您对整数输入没有同样的问题。您可能还需要一个单独的函数将小数部分转换为二进制。
  • 这是因为scanf("%d",&userDec); 从输入中取出字符,直到找到与格式字符串不匹配的字符。因此,如果您输入12.34,scanf 将获取12,然后找到.,但%d 格式中不允许使用小数点,因此它会停止使用字符。然后转换 12 并返回到 scanf 但 scanf 找到的第一个字符是 . 并且它不能使用它所以它停止。但是你不检查返回码。要修复它,请检查 scanf 的返回码,如果失败,则 f 获取该行的其余部分以收集 .34\n,然后再次 scanf。
  • res = (int)(res); - 这是一个完全没用的声明。它打算做什么?
  • @Amy int没有小数部分

标签: c floating-point integer


【解决方案1】:

条件userDec % 1 == 0始终为真,任何整数除以1的余数始终为0。

scanf("%d", &userDec) 无法解析输入的值时,它会返回 0,当您输入的不是数字时会发生这种情况,您可以利用这一点。

要检查一个值是否有小数部分,我们可以使用 math.h 库函数 modf 它将双精度的小数部分与其整数部分分开并返回它,所以是的,这将是一个很好的策略double(而不是不太精确的 float)。

(在 Linux 中,使用 gcc 您可能需要使用 -lm 编译器标志来链接 math.h 标头。)

将这些更改应用到您的代码中,您将拥有如下内容:

#include <stdlib.h>
#include <math.h>
int main(void)
{
    double userDec;
    int res;
    double integral;

    while (1) 
    {
        printf("Please enter a positive whole number (or EOF to quit): ");
        res = scanf("%lf", &userDec);
        if (res == EOF) break;

        // if res = 0 input was not a number, if decimal part is 0 it's an integer
        if (res == 0 || userDec < 0 || modf(userDec, &integral))
        {
            printf("Sorry, that was not a positive whole number.\n");
            int c;
            while ((c = getchar()) != '\n' && c != EOF){} // clear buffer
            if (c == EOF)
            {
                return EXIT_FAILURE; // safety measure in case c = EOF
            }
        }
        else
        {
            printf("%d (base-10) is equivalent to ", res);
            DecToBin(userDec);
            printf(" (base-2)!");
            printf("\n");
        }
    }
    printf("\n");
    return EXIT_SUCCESS;
}

考虑到您的规格,此代码的输入 11.0 将是有效的,因为它的小数部分是 0,而像 11.00000001 这样的东西不是,因为当然,它的小数部分是不是0


上面的代码有一个漏洞,如果输入的值较大INT_MAXscanf没有处理它的工具。如果您真的想认真对待这一点,您可以将输入解析为字符串并将其转换为strtod

#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <errno.h> // may be needed for errno
int main(void)
{
    double userDec;
    double integral;
    char input[100];
    char *endptr = NULL;
    int c;

    // parse input as string
    while (printf("Please enter a positive whole number (or EOF to quit): ") && scanf("%99[^\n]", input) == 1)
    {
        errno = 0;
        userDec = strtod(input, &endptr); // convert to double
        
        // if negative or overflow or invalid input or fractional
        if (userDec < 0 || (userDec == HUGE_VAL && errno == ERANGE) || *endptr != '\0' || modf(userDec, &integral))
        {
            printf("Sorry, that was not a positive whole number.\n");
        }
        else
        {
            printf("%.0lf (base-10) is equivalent to ", userDec);
            DecToBin(userDec);
            printf(" (base-2)!\n");
        }
        while ((c = getchar()) != '\n' && c != EOF) { continue; } // clear buffer
        if (c == EOF) { return EXIT_FAILURE; }   // c = EOF, treated as unrecoverable
    } 
    return EXIT_SUCCESS;
}

当然,对于这样的事情,您需要支持您的转换器函数以接受更大的值,它现在只接受 int 作为参数。

无论哪种方式,您的代码中的所有这些输入限制都应该方便地记录在案。

【讨论】:

  • 哦,是的,对不起,我想我在最初的问题中没有对自己做出太多解释。我希望输入 11.1 告诉用户它不起作用。我的代码只是重复“请输入...”语句。那么我应该以浮点数的形式扫描吗?
  • @osito_solito,是的,最好将其解析为double,这样我们就可以检查小数部分,scanf 只是丢弃小数部分,有一些方法可以检查,但使用起来更简单modf 如上代码所示。
【解决方案2】:

最强大的方法不使用scanf() 来读取用户输入的。而是使用fgets(),然后用strto*() 和朋友解析字符串

由于 OP 正在寻找作为整数或具有整数值的浮点数的有效输入,因此构造辅助例程以做好每项工作。

#include <ctype.h>
#include <errno.h>
#include <stdio.h>

void base(int n) {
  if (n == 0)
    return;
  base(n / 2);
  printf("%d", n % 2);
}
// Return error flag, 0 implies success
int String2int(int *dest, const char *s, int min, int max) {
  if (dest == NULL)
    return 1;
  *dest = 0;
  if (s == NULL)
    return 1;

  errno = 0;
  char *endptr;
  long L = strtol(s, &endptr, 0);

  if (s == endptr)
    return 1;  // no conversion
  if (errno == ERANGE || L < min || L > max)
    return 1; // out of range
  while (isspace((unsigned char) *endptr))
    endptr++;
  if (*endptr)
    return 1;  // Extra text after input
  *dest = (int) L;
  return 0;
}

int String2Whole(double *dest, const char *s, double min, double max_plus1) {
  if (dest == NULL)
    return 1;
  *dest = 0.0;
  if (s == NULL)
    return 1;

  errno = 0;
  char *endptr;
  double d = strtof(s, &endptr);

  if (s == endptr)
    return 1;  // no conversion
  // Save whole part into dest
  if (modf(d, dest))
    return 1; // Test return of non-zero fractional part.
  if (d < min || d >= max_plus1)
    return 1; // out of range
  while (isspace((unsigned char) *endptr))
    endptr++;
  if (*endptr)
    return 1;  // Extra text after input
  return 0;
}

配备 2 个对转换很挑剔的辅助函数,尝试一个,然后根据需要尝试另一个。

char *doit(void) {
  int i = 0;
  char buf[100];
  if (fgets(buf, sizeof buf, stdin) == NULL) {
    return "Input closed";
  }

  if (String2int(&i, buf, 0, INT_MAX)) {
    double x;
    if (String2Whole(&x, buf, 0.0, (INT_MAX / 2 + 1) * 2.0)) {
      return "Invalid input";
    }
    i = (int) x;
  }
  printf("O happy day! %d\n", i);
  return 0;
}

int main(void) {
    doit();
}

【讨论】:

  • @anastaciu 已更新。
【解决方案3】:

请记住,scanf 返回成功转换和分配的数量,或者 EOF 返回文件结尾或读取错误。

%d 转换说明符将跳过前导空格并读取十进制数字直到第一个非十进制数字字符。 如果第一个非空白字符不是十进制数字,那么您有一个匹配失败并且scanf 将返回一个0不是@987654326 @.

输入11.1时,scanf成功转换11赋值给userDec并返回1。但是,它将剩余的 .1 留在输入流中 - 永远不会被清除。

在下一次读取时,scanf 看到 .1 并立即停止读取并返回 0。但是,您只是在测试 scanf 不会返回 EOF,因此它永远不会跳出循环。

使用scanf,您最好针对您希望成功转换的项目数量进行测试 - 在这种情况下,您的测试应该是

if ( res != 1 )
  break;

我会重组你的阅读循环如下:

while ( 1 )
{
  // prompt

  if ( (res = scanf( "%d", &userDec)) != 1 )
  {
    // print error message
    break;
  }
  else
  {
    if ( userDec <= 0 )
    {
      // print error message
    }
    else
    {
      // process input
    }
  }
}
if ( res != EOF )
{
  // had a matching failure from bad input
}
else
{
  // user signaled EOF
}

由于%d 转换说明符接受整数作为输入,并且由于userDec 只能保存整数值,因此无需测试userDec 本身的整数性。 userDec % 1 == 0 表达式将始终为真,在这种情况下毫无意义。

如果您想验证用户输入的是整数字符串还是其他内容,那么您需要做更多的工作。您必须检查scanf 的返回值并且您必须测试%d 接受后的第一个字符:

 int itemsRead;
 int userDec;
 char dummy;

 /**
  * Read the next integer input and the character immediately
  * following.
  */
 itemsRead = scanf( "%d%c", &userDec, &dummy );
 
 /**
  * Here are the possible scenarios:
  *
  * 1.  If itemsRead is 2, then we read at least one decimal digit
  *     followed by a non-digit character.  If the non-digit character
  *     is whitespace, then the input was valid.  If the non-digit
  *     character is *not* whitespace, then the input was not valid.
  *
  * 2.  If itemsRead is 1, then we read at least one decimal digit
  *     followed immediately by EOF, and the input was valid.
  *
  * 3.  If itemsRead is 0, then we had a matching failure; the first
  *     non-whitespace character we saw was not a decimal digit, so the
  *     input was not valid.
  *
  * 4.  If itemsRead is EOF, then we either saw an end-of-file condition 
  *     (ctrl-d) before any input, or there was a read error on the input
  *     stream.
  *  
  * So, our test is if itemsRead is less than 1 OR if itemsRead is 2 and 
  * the dummy character is not whitespace
  */
 if ( itemsRead < 1 || itemsRead == 2 && !isspace( dummy ) )
 {
   // input was not valid, handle as appropriate.
 }
 else
 {
   // input was valid, process normally
 }

【讨论】:

    猜你喜欢
    • 2018-06-26
    • 1970-01-01
    • 2020-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-05
    相关资源
    最近更新 更多