KhronosGroup / SPIRV-Cross

SPIRV-Cross is a practical tool and library for performing reflection on SPIR-V and disassembling SPIR-V back to high level languages.
Apache License 2.0
2.03k stars 557 forks source link

generate the smallest float/double that preserves the binary representation #2132

Open pixelflinger opened 1 year ago

pixelflinger commented 1 year ago

Currently spirv-cross uses %.32g by default to format floats, this can be overridden with SPIRV_CROSS_FLT_FMT at compile time. When embedding shaders with binaries, reducing their binary footprint can be important.

What really matters is to preserve the binary representation of the float in the SPIRV file. 32 digits is overkill for floats and doubles. We could use respectively 9 and 17 digits, which is always enough, however, this would still generate strings larger than needed.

Ideally, we'd want to use the smallest string that preserves the binary representation.

For instance, I have tested the following implementation for floats only:

static inline std::string convert_to_smallest_string(float f, char locale_radix_point) {
    char buf[16]; // e.g.: -0.12345678e-12, 16 bytes needed
    float r;
    for (int i = 1; i < 9; i++) {
        sprintf(buf, "%.*g", i, f);
        sscanf(buf, "%f", &r);
        if (r == f) {
            break;
        }
    }
    fixup_radix_point(buf, locale_radix_point);

    // Ensure that the literal is float.
    if (!strchr(buf, '.') && !strchr(buf, 'e'))
        strcat(buf, ".0");

    return { buf };
}

For doubles and long doubles it might better to binary-search the right format length.

HansKristian-Work commented 1 year ago

We could use respectively 9 and 17 digits, which is always enough

This approach failed in one of my tests, but maybe I did something wrong. Either way, shortening the string would be great, it's been a long standing todo that just never bubbled up as something critical, but we need to have a strategy on how we're going to verify it if we're going to implement this. For FP32 we can roundtrip every possible value in a reasonable amount of time, and we should be able to test a reasonable subset of FP64 as well.

pixelflinger commented 1 year ago

@HansKristian-Work there is some good information here (as to why %.9g works for floats): https://randomascii.wordpress.com/2013/02/07/float-precision-revisited-nine-digit-float-portability