Skip to content

Stream::parseFloat() fails if it reads too many digits #129

Closed
@edgar-bonet

Description

@edgar-bonet

Stream::parseFloat() builds a long integer out of the digits it reads, skipping the decimal point, and then it scales it by the appropriate power of 0.1. For example, "3.14" is interpreted as 314 × 0.12.

If the user provides too many digits after the decimal point, the integer overflows, which triggers undefined behavior and makes the function return garbage. For example, "3.141592654" is read as 3141592654 × 0.19, but the integer overflows to −1153374642, and Stream::parseFloat() returns −1.1534.

It could be argued that it is the user's fault, and that they should just not provide more digits than needed by the resolution of the float data type. The users, however, are not always in control of the format of the data they want to process. I have found an unsuspecting user bitten by this issue.

Test sketch: This uses a class that makes an input Stream out of a string, in order to not be dependent on the serial monitor.

// Stream in a C string.
class StrStream : public Stream {
public:
    StrStream(const char *s_in) : s(s_in) {}
    size_t write(uint8_t) { return 0; }
    int available() { return strlen(s); }
    int read() { return *s ? *s++ : -1; }
    int peek() { return *s ? *s : -1; }
private:
    const char *s;
};

void setup() {
    Serial.begin(9600);
    Serial.println(StrStream("3.1415927 ").parseFloat());
    Serial.println(StrStream("3.14159265 ").parseFloat());
    Serial.println(StrStream("3.141592654 ").parseFloat());
    Serial.println(StrStream("3.1415926536 ").parseFloat());
}

void loop(){}

Output:

3.14
3.14
-1.15
0.14

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions