Search code examples
c++chexcode-generationlossless

Compact lossless representation of floating point constants in C/C++


I have a program written in C++ which is generating C source code for mathematical calculations. I have noticed that the constants take up very much space in the generated code and am looking for a more compact representation.

To generate constants, I am now using:

double v = ...
cfile << std::scientific << std::setprecision(std::numeric_limits<double>::digits10 + 1) << v;

I am pretty sure that this is a lossless representation, but it is also very bloated. For example a zero and a one would be represented as something like 0.0000000000000000e+00 and 1.0000000000000000e+00. And "0." or "1." carries just as much information.

Is there a way to print constants to file in a more compact, but still lossless manner? It does not need to look good for a human reader, just compile when present in plain C code (if C99, I would prefer if it's also valid C++). Hexadecimal could be ok if it is portable.

EDIT: Removed std::fixed in code snippet.


Solution

  • This is not a problem of representation, language or standard library but of algorithm. If you have a code generator then...why don't you change the generated code to be the best (= shortest with required precision) representation? It's what you do when you write code by hand.

    In the hypothetical put_constant(double value) routine you may check what's the value you have to write:

    • Is it an integer? Don't bloat the code with std::fixed and set_precision, just cast to integer and add a dot.
    • Try to convert it to string with default settings then convert it back to double, if nothing changed then default (short) representation is good enough.
    • Convert it to string with your actual implementation, and check its length. If it's more than N (see later) use another representation otherwise just write it.

    A possible (short) representation for floating point numbers when they have a lot of digits is to use their memory representation. With this you have a pretty fixed overhead and length won't ever change so you should apply it only for very long numbers. A naive example to show how it may work:

    #define USE_L2D __int64 ___tmp = 0;
    #define L2D(x) (double&)(___tmp=x)
    
    int main(int argc, char* argv[])
    {
        // 2.2 = in memory it is 0x400199999999999A
    
        USE_L2D
        double f1 = L2D(0x400199999999999A);
        double f2 = 123456.1234567891234567;
    
        return 0;
    }