当输入为浮点数时如何阻止scanf解析整数部分

How to stop scanf from parsing the integral part when the input is a float

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

我希望我的代码重复直到用户按下 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("%d", &userDec)无法解析输入的值时,它returns 0,当您输入的不是数字时会发生这种情况,您可以利用它。

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

(在 Linux 中,使用 gcc 您可能需要将 -lm 编译器标志用于 link math.h header。)

将这些更改应用到您的代码中,您将得到如下结果:

#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 != '[=13=]' || 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 作为参数。

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

记住 scanf return 是成功转换和赋值的次数,或者 EOF 文件末尾或读取错误。

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

当您输入 11.1 时,scanf 成功转换并将 11 分配给 userDec 和 returns 1。然而,它将剩余的 .1 留在输入流中 - 永远不会被清除。

在下一次阅读时,scanf 看到 .1 并立即停止阅读,return 看到 0。但是,您只是在测试 scanf 不会 return 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 的 return 值 您必须测试 %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
 }

最可靠的方法不使用 scanf() 来读取 的用户输入。而是使用 fgets() 然后用 strto*() 和 friends.

解析 string

由于 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();
}