2
\$\begingroup\$

I always wanted to try fbld x87 instruction to aid to convert string to floating point value.

I sure a chance to face this instruction in disassembly of all the code written for last decades (Linux kernel, math soft etc) is as close to 0 as possible. No one another instruction is as of no use as this one. For decades! From later 80'.

Here is my attempt:

#include <sstream>
#include <iostream>
#include <iomanip> 

#include <cstdio>
#include <cctype>
#include <cstdint>
#include <cassert> 
#include <cmath> 

namespace io
{
namespace
{

char input[1 << 24];
auto cursor = input;
auto size = []
{
    char x[] = "+1.5E+1";
    for (auto s : {"", "-", "+"}) {
    auto y = x;
    while (*s) {
        *y++ = *s++;
    }
    for (auto m : {"1", "1.", ".1", "1.1"}) {
        auto z = y;
        while (*m) {
            *z++ = *m++;
        }
        cursor = std::copy(x, z, cursor);
        *cursor++ = ' ';
        for (auto E : {'E', 'e'}) {
            *z = E;
            for (auto S : {"", "-", "+"}) {
                auto t = z + 1;
                if (*S) {
                    *t++ = *S++;
                }
                *t++ = '1';
                assert(t < x + sizeof x);
                cursor = std::copy(x, t, cursor);
                *cursor++ = ' ';
            }
        }
    }
    }
    return cursor;
}() - input;

using i32 = std::int32_t;
using i64 = std::int64_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;

void skip_ws()
{
    for (;;) {
    switch (*++cursor) {
    case ' ' :
    case '\t' :
    case '\r' :
    case '\n' : {
        break;
    }
    default : {
        return;
    }
    }
    }
}

template< typename F >
void read_float(F & result)
{
    static_assert(std::is_floating_point< F >::value, "!");
    char c = *cursor;
    std::uint8_t significand[std::numeric_limits< F >::digits10];
    auto s = significand;
    std::int32_t after = 0;
    std::int32_t before = 0;
    char sign = c;
    switch (c) {
    case '-' :
    case '+' :
    c = *++cursor;
    break;
    default : {
    break;
    }
    }
    [&]
    {
    bool d = false;
    for (;;) {
        switch (c) {
        case '.' :
            before = 1;
            break;
        case '0' ... '9' : {
            if (c != '0') {
                d = true;
            }
            if (0 < before) {
                ++before;
            }
            if (d) {
                *s++ = (c - '0');
                if (s == significand + sizeof significand) {
                    std::int32_t a = 0;
                    for (;;) {
                        switch ((c = *++cursor)) {
                        case '0' ... '9' :
                            ++a;
                            break;
                        case '.' : {
                            after = a;
                            break;
                        }
                        default : {
                            if ((before == 0) && (after == 0)) {
                                after = a;
                            }
                            return;
                        }
                        }
                    }
                }
            }
            break;
        }
        default : {
            if (!d) {
                *s++ = 0;
            }
            return;
        }
        }
        c = *++cursor;
    }
    }();
    if (0 < before) {
    after -= (before - 1);
    }
    std::uint32_t exponent = 0;
    switch (c) {
    case 'e' :
    case 'E' : {
    c = *++cursor;
    char esign = c;
    switch (c) {
    case '-' :
    case '+' :
        c = *++cursor;
        break;
    }
    [&]
    {
        for (;;) {
            switch (c) {
            case '0' ... '9' :
                exponent = (exponent << 3) + (exponent << 1) + (c - '0');
                break;
            default : {
                return;
            }
            }
            c = *++cursor;
        }
    }();
    if (esign == '-') {
        after -= exponent;
    } else {
        after += exponent;
    }
    break;
    }
    default : {
    break;
    }
    }

    alignas(32) std::uint8_t bcd[10] = {};
    std::uint32_t b = 0;
    do {
    --s;
    if ((b % 2) == 0) {
        bcd[b / 2] = *s;
    } else {
        bcd[b / 2] |= (*s << 4);
    }
    ++b;
    } while (s != significand);

    if (sign == '-') {
    bcd[9] = (1 << 7);
    }

    asm(
    "fldl2t;"
    "fildl %[exp10];"
    "fmulp;"
    "fld %%st;"
    "frndint;"
    "fxch;"
    "fsub %%st(1), %%st;"
    "f2xm1;"
    "fld1;"
    "faddp;"
    "fscale;"
    "fstp %%st(1);"
    "fbld %[tbyte];"
    "fmulp;"
    : "=t"(result)
    : [exp10]"m"(after), [tbyte]"m"(bcd)
    : "memory", "st(1)", "st(2)"
    );
}

}
}

int main()
{
    using namespace io;
    //size = std::fread(input, 1, sizeof input, stdin);
    //*cursor = '\0';
    //cursor = input;

    {
    static const char l[] = "-.999999999999999999E-124";
    cursor = std::copy(l, l + sizeof l - 1, cursor);
    *cursor++ = ' ';
    }

    {
    static const char h[] = "0.999999999999999999187";
    cursor = std::copy(h, h + sizeof h - 1, cursor);
    *cursor++ = ' ';
    }

    {
    static const char g[] = "9999999999999999999999999999999999999999";
    cursor = std::copy(g, g + sizeof g - 1, cursor);
    *cursor++ = ' ';
    }

    *cursor++ = '\0';

    //std::cout << input << std::endl;

    cursor = input;
    switch (*cursor) {
    case ' ' :
    case '\t' :
    case '\r' :
    case '\n' :
    skip_ws();
    break;
    case '\0' :
    return EXIT_SUCCESS;
    }
    std::istringstream is{input};
    std::stringstream ss{input};
    std::cout << std::setprecision(std::numeric_limits< long double >::digits10 + 1);
    for (;;) {
    std::string x;
    if (!(is >> x)) {
        break;
    }
    long double y = std::numeric_limits< long double >::quiet_NaN();
    if (!(ss >> y)) {
        assert(false);
    }
    long double z;
    read_float(z);
    if (std::abs(z - y) > 9E-18 * std::max(std::abs(z), std::abs(y))) {
        std::cout << x << ":\t" << y << '\t' << z << std::endl;
    }
    skip_ws();
    }
#if 0
    using c8 = char [8];
    const auto s2u32 = [] (const char s[]) -> std::uint32_t
    {
    static c8 z = {'0', '0', '0', '0', '0', '0', '0', '0'};
    auto string = reinterpret_cast< const std::uint64_t & >(*s) - reinterpret_cast< const std::uint64_t & >(z);
    string = (((string >> 4) & 0x00F000F000F000F0LL) | (string & 0x000F000F000F000FLL));
    c8 & c = reinterpret_cast< c8 & >(string);
    c[1] = c[2];
    c[2] = c[4];
    c[3] = c[6];
    return reinterpret_cast< const std::uint32_t & >(c);
    };
    auto bcd = s2u32("01234567");
    std::cout << std::hex << bcd << std::endl;
    // fbld
#endif
}

It's precision is worse then one for <istream>, but I think it can be enough for many practical tasks.

Runtime speed is not tested. But I interested in.

What can be improved?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.