Serenity: LibC/LibJS: strtod() weirdness

Created on 26 Apr 2020  路  3Comments  路  Source: SerenityOS/serenity

This test is working fine on my host machine but fails on Serenity:

https://github.com/SerenityOS/serenity/blob/14c7988eeafd67722bbc246812d536345924c078/Libraries/LibJS/Tests/Number.isInteger.js#L11

I did some digging, the issue seems to be with Serenity's strtod() implementation:

https://github.com/SerenityOS/serenity/blob/95b51e857d8f77f8bf9c4e01a4388d42a4f49887/Libraries/LibJS/Token.cpp#L72

Simple program to reproduce:

#include <AK/LogStream.h>
#include <stdlib.h>

int main(int, char**)
{
    double d1 = strtod("5.000000000000001", nullptr);
    double d2 = strtod("5.0000000000000001", nullptr);
    dbg() << (((i32)d1 == d1) ? "(i32)d1 == d1" : "(i32)d1 != d1");
    dbg() << (((i32)d2 == d2) ? "(i32)d2 == d2" : "(i32)d2 != d2");
}

Serenity:

(i32)d1 != d1
(i32)d2 != d2

Linux (via Lagom):

(i32)d1 != d1
(i32)d2 == d2

Some more weird stuff happening:

> 1.0000000000000000000000000000001
1.0000
> 1.00000000000000000000000000000001
Infinity
> 1.12345678912345678912345678912345
Infinity
> 1.123456789123456789123456789123456789123456789
Infinity

Most helpful comment

Here's a few fun bits, maybe for entertainment, maybe for documentation:

  • 4e3e4 becomes 40000.0, because the second exponent "overwrites" the first.
  • atof thinks that 5e-3 is 5000, because atof ignores a minus sign in the exponent. strtod handles that correctly. (Also, why the duplicated code? Also, why is the exponent sign handled manually when the atoi implementation could handle it?)
  • 7e0 becomes 70.0, because the e is ignored if it is followed by only-zeros.
  • 1e-4294967296 becomes 14294967296.0, because it attempts to read 4294967296 as a positive int, this overflows, atol returns 0 out of spite (seriously), and strtod just continues as if nothing had happened.
  • 1e-599999999 becomes 0.0 correctly, but takes a lot of time for it (because it divides by 10 very, very often).
  • The test suite contains a lot of zeros, so the "implementation" return 0.0; makes more tests green than it makes red.

I'll also touch up atoi and related code.

All 3 comments

That reminds me a lot of Rust's struggles with float literals. You may find these test cases helpful.

For the record: I'm having a look at whether I can write a strtod myself. Here's a peek.

Here's a few fun bits, maybe for entertainment, maybe for documentation:

  • 4e3e4 becomes 40000.0, because the second exponent "overwrites" the first.
  • atof thinks that 5e-3 is 5000, because atof ignores a minus sign in the exponent. strtod handles that correctly. (Also, why the duplicated code? Also, why is the exponent sign handled manually when the atoi implementation could handle it?)
  • 7e0 becomes 70.0, because the e is ignored if it is followed by only-zeros.
  • 1e-4294967296 becomes 14294967296.0, because it attempts to read 4294967296 as a positive int, this overflows, atol returns 0 out of spite (seriously), and strtod just continues as if nothing had happened.
  • 1e-599999999 becomes 0.0 correctly, but takes a lot of time for it (because it divides by 10 very, very often).
  • The test suite contains a lot of zeros, so the "implementation" return 0.0; makes more tests green than it makes red.

I'll also touch up atoi and related code.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

danboid picture danboid  路  9Comments

BenWiederhake picture BenWiederhake  路  8Comments

ZyorYT picture ZyorYT  路  6Comments

Arthur-Kamau picture Arthur-Kamau  路  7Comments

awesomekling picture awesomekling  路  6Comments