简体   繁体   中英

C++ program behavior

Was demonstrating the failure of C++ to detect input issues in floating point numbers when additional characters follow the input.

EG 234.5 when using cin works fine, but 234.t (demonstrating a finger slip on input) would read the 234. and leave the t.

Imagine my surprise when some input fails and others did not. Note: These are not meant to be complex validators, just a demo to intro students that input can be tricky to validate.

Program compiled on a Mac in Clion under C++ 14

#include <iostream>
#include <fstream>

int main() {

    float number = 0.0;
    int counter = 0;
    std::ifstream inData;
    inData.open("input.txt");

    inData >> number;
    for(int i = 0; i < 26;i++ ){
        if(inData.good()) {
            std::cout << "Good Row number: " << i << " " << static_cast<char>(i+97) << std::endl;
        } else {
            std::cout << "\tFail Row number: " << i << " " << static_cast<char>(i+97) << std::endl;
            inData.clear();
            inData.ignore(1, '\n');
        }

        inData >> number;
    }

    return 0;
}

Note: I ran this using this input file for curiosity sake

234.a
234.b
234.c
234.d
234.e
234.f
234.g
234.h
234.i
234.j
234.k
234.l
234.m
234.n
234.o
234.p
234.q
234.r
234.s
234.t
234.u
234.v
234.w
234.x
234.y
234.z

and this is the output:

    Fail Row number: 0 a
    Fail Row number: 1 b
    Fail Row number: 2 c
    Fail Row number: 3 d
    Fail Row number: 4 e
    Fail Row number: 5 f
Good Row number: 6 g
    Fail Row number: 7 h
Good Row number: 8 i
    Fail Row number: 9 j
    Fail Row number: 10 k
Good Row number: 11 l
    Fail Row number: 12 m
Good Row number: 13 n
    Fail Row number: 14 o
Good Row number: 15 p
    Fail Row number: 16 q
Good Row number: 17 r
    Fail Row number: 18 s
    Fail Row number: 19 t
Good Row number: 20 u
    Fail Row number: 21 v
    Fail Row number: 22 w
Good Row number: 23 x
    Fail Row number: 24 y
Good Row number: 25 z

Anyone have any clue as to what is going on?

Notes on comments: -yes, using 'a' vs 97 is better, but part of the lesson is to emphasize that characters have a decimal value hence they are easy to compare.

  • provided
  • simplified for the core issue. I'll work on the second part for later.

This seems to be one of the inconsistencies in parsing floating point numbers that occasionaly surface in the implementations of the standard library (see eg 1169 , 2381 ).

Consider the following modified snippet

#include <iostream>
#include <sstream>
#include <string>

int main()
{
    float number{};
    std::string test{ "234.0" };
    
    for(char ch{'a'}; ch <= 'z'; ++ch )
    {
        test.back() = ch;
        std::istringstream ss{ test };
        
        if (ss >> number)
            std::cout << ch << ' ';
    }
    std::cout << '\n';
}

Here you can see that, when compiled with Clang and -stdlib=libc++ it produces the following output

g h j k l m o q r s t u v w y z 

While -stdlib=libstdc++ gives

a b c d f g h i j k l m n o p q r s t u v w x y z     // Only 'e' is rejected

Using strtof , all the cases are rejected (see eg here ).

operator>>

(5) Extracts a floating-point value potentially skipping preceding whitespace. The value is stored to a given reference value. This function behaves as a FormattedInputFunction . After constructing and checking the sentry object, which may skip leading whitespace, extracts a floating-point value by calling std::num_get::get() .

The Standard specifies the behavior of std::num_get::get() at 28.4.3.2 [locale.num.get] .

There are three stages

  1. Determine a conversion specifier
  2. Extract characters from in and determine a corresponding char value for the format expected by the conversion specification determined in stage 1.
  3. Store results

The details of stage 2 mention that

If it is not discarded, then a check is made to determine if c is allowed as the next character of an input field of the conversion specifier returned by Stage 1. If so, it is accumulated.

While on stage 3

The sequence of chars accumulated in stage 2 (the field) is converted to a numeric value by the rules of one of the functions declared in the header:
[...]
For a float value, the function strtof .
[...]
The numeric value to be stored can be one of:

  • zero, if the conversion function does not convert the entire field.
  • [...]
  • the converted value, otherwise.

The resultant numeric value is stored in val.
If the conversion function does not convert the entire field , or if the field represents a value outside the range of representable values, ios_base::failbit is assigned to err .

Given the previous results, it appears that stage 2 is not correctly implemented in both the tested library.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM