How to flip the exponent of a double (e.g. 1e300->1e-300)?

前端 未结 1 560
清歌不尽
清歌不尽 2021-01-29 06:49

I am interested in writing a fast C program that flips the exponent of a double. For instance, this program should convert 1e300 to 1e-300. I guess the best way would be some

相关标签:
1条回答
  • 2021-01-29 07:37

    Assuming you mean to negate the decimal exponent, the power of ten exponent in scientific notation:

    #include <math.h>
    
    double negate_decimal_exponent(const double value)
    {
        if (value != 0.0) {
            const double p = pow(10.0, -floor(log10(fabs(value))));
            return (value * p) * p;
        } else
            return value;
    }
    

    Above, floor(log10(fabs(value))) is the base 10 logarithm of the absolute value of value, rounded down. Essentially, it is the power of ten exponent in value using the scientific notation. If we negate it, and raise ten to that power, we have the inverse of that power of ten.

    We can't calculate the square of p, because it might underflow for very large values of value in magnitude, or overflow for very small values of value in magnitude. Instead, we multiply value by p, so that the product is near unity in magnitude (that is, decimal exponent is zero); then multiply that with p, to essentially negate the decimal exponent.

    Because the base-ten logarithm of zero is undefined, so we need to deal with that separately. (I initially missed this corner case; thanks to chux for pointing it out.)

    Here is an example program to demonstrate:

    #include <stdlib.h>
    #include <stdio.h>
    #include <math.h>
    
    double negate_decimal_exponent(const double value)
    {
        if (value != 0.0) {
            const double p = pow(10.0, -floor(log10(fabs(value))));
            return (value * p) * p;
        } else
            return value;
    }
    
    #define TEST(val) printf("negate_decimal_exponent(%.16g) = %.16g\n", val, negate_decimal_exponent(val))
    
    int main(void)
    {
        TEST(1.0e300);
        TEST(1.1e300);
        TEST(-1.0e300);
        TEST(-0.8e150);
        TEST(0.35e-25);
        TEST(9.83e-200);
        TEST(23.4728395e-220);
        TEST(0.0);
        TEST(-0.0);
    
        return EXIT_SUCCESS;
    }
    

    which, when compiled (remember to link with the math library, -lm) and run, outputs (on my machine; should output the same on all machines using IEEE-754 Binary64 for doubles):

    negate_decimal_exponent(1e+300) = 1e-300
    negate_decimal_exponent(1.1e+300) = 1.1e-300
    negate_decimal_exponent(-1e+300) = -1e-300
    negate_decimal_exponent(-8e+149) = -8e-149
    negate_decimal_exponent(3.5e-26) = 3.5e+26
    negate_decimal_exponent(9.83e-200) = 9.83e+200
    negate_decimal_exponent(2.34728395e-219) = 2.34728395e+219
    negate_decimal_exponent(0) = 0
    negate_decimal_exponent(-0) = -0
    

    Are there faster methods to do this?

    Sure. Construct a look-up table of powers of ten, and use a binary search to find the largest value that is smaller than value in magnitude. Have a second look-up table have the two multipliers that when multiplied with value, negates the decimal power of ten. Two factors are needed, because a single one does not have the necessary range and precision. (However, the two values are symmetric with respect to the base-ten logarithm.) For a look-up table with thousand exponents (covers IEEE-754 doubles, but one should check at compile time that it does cover DBL_MAX), that would be ten comparisons and two multiplications (using floating-point values), so it'd be quite fast.

    A portable program could calculate the tables necessary at run-time, too.

    0 讨论(0)
提交回复
热议问题