-1

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 bit operations, but I lack enough knowledge to fulfill that. Any good idea?

zell
  • 9,830
  • 10
  • 62
  • 115
  • 1
    A good start would be to look at the IEEE float format. https://en.wikipedia.org/wiki/Double-precision_floating-point_format – Max Jul 04 '17 at 00:32
  • @DavidBowling And what should OP do to achieve the equivalent effect on any arbitrary floating point value? – Cloud Jul 04 '17 at 00:33
  • 1
    Neither `1e300` or `1e-300` can be exactly represented with a [binary64](https://en.wikipedia.org/wiki/Double-precision_floating-point_format). How much error is tolerable? – chux - Reinstate Monica Jul 04 '17 at 00:36
  • This doesn't seem at all straightforward. In the binary representation, the exponent is a power of 2, not a power of 10. There's no direct correlation between `1e300` and `1e-300` in binary floating point. – Barmar Jul 04 '17 at 00:57
  • 1
    Looks like an XY problem. That's not really useful. If you want to take the reziprok or divide, use ` 1 / x`. A good compiler might already optimise this to your platform's optimum code. Don't do premateure optimisations. If your maintainable code is too slow, profile and optimise the identified hotspots. – too honest for this site Jul 04 '17 at 01:25
  • 1
    @zell: You might wish to reword your question to for example *"How to efficiently negate the decimal exponent (following e in scientific notation) of a floating-point value in C?"*. (If, in fact, that is what your question is about.) – Nominal Animal Jul 04 '17 at 02:00
  • 5
    Does this mean you also want `2e100` to become `2e-100`? This is a strange operation that is not very meaningful mathematically. Why do you need to do this? – Raymond Chen Jul 04 '17 at 03:01

1 Answers1

2

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.

Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • Nicely done. Might want to post the result of `negate_decimal_exponent(0.0)` – chux - Reinstate Monica Jul 04 '17 at 01:24
  • @chux: Thanks for the heads-up! Indeed, the old version of the function incorrectly returned `-HUGE_VAL`. Now fixed, with explanation in the text. – Nominal Animal Jul 04 '17 at 01:52
  • @NominalAnimal Thanks! I wonder whether it is possible to achieve this through bit manipulation, e.g, modify the bits of the exponent *in place* so that it corresponds to the negation of the original one? – zell Jul 04 '17 at 02:23
  • @zell: No. IEEE-754 Binary64, which is probably what your machine uses for `double`s, is a binary format, whereas we need to operate on powers of ten (including negative powers, i.e. divisions by powers of ten). – Nominal Animal Jul 04 '17 at 02:31
  • 1
    @zell: On the other hand, for Binary64, you can do the power-of-ten tables for all powers of two, and simply use the 11 exponent bits of the value as indexes to the coefficient table. That saves you the binary search. Essentially, you can use the Binary64 exponent bits of `value` to look up `pow(10.0, -floor(log10(value)))`; still, you do need to do the two floating-point multiplications, those cannot be avoided. – Nominal Animal Jul 04 '17 at 02:34