2

edit: To clarify, by "decimal exponent of a double" I mean the magnitude of the value or the base 10 exponent of the value. For instance 3456.7 -> 3.4567E3 -> Scientific Exponent = 3

I need to find the decimal exponent of a double.

I've been researching ways this is done, and I've found various methods.

For instance to estimate the exponent Microsoft's STL uses an interesting lookup table:

However, the method I came up with seems the simplest:

  // v -> value
  // x -> ScientificExponent
  // 10^x <= v < 10^(x+1)
  // x <= log(v) < x+1

  ScientificExponent = (Value == 0) ? 1 : floor((log10(fabs(Value))));

I'm wondering why this isn't used. Are there cases where this doesn't work? Is this assumed to be too slow?

Are there any edge/corner cases I am not considering (maybe subnormal values being too small to get an accurate log calculation from)?

I'd appreciate any insight people can provide.

Also, if this is a good way to do this, is there a good way to calculate only the integer part of log10? It seems a waste to calculate the log to many decimal places only to throw it away with the floor.

Adam Bujak
  • 63
  • 7
  • This may be helpful: https://stackoverflow.com/questions/25892665/performance-of-log10-function-returning-an-int – nielsen Aug 18 '23 at 19:45
  • That answers the last question in my post, thanks! I'm still wondering about why this method isn't used for exponent estimation, what the drawbacks/advantages are, and if this is a viable method – Adam Bujak Aug 18 '23 at 19:46
  • It's generally safe to assume that the method used in common libraries is chosen for performance reasons. We've had decades to determine the optimal method. – Barmar Aug 18 '23 at 19:50
  • That LUT in STL is optimized for determining only if it is within the fixed point range so it's not suitable for all of my applications anyway. If you could provide another method that has been optimized for this that would be great! I haven't been able to find any "accepted" solution for this – Adam Bujak Aug 18 '23 at 19:57
  • If you have floating-point inputs then the standard [`frexp()`](https://linux.die.net/man/3/frexp) function might be of interest to you. That will quickly and easily get you the exponent of the *base-2* exponential notation (of the form 0.1xxxxxE+-n). – John Bollinger Aug 18 '23 at 20:23
  • @AdamBujak "quickly and accurately" --> which is more important? speed or accuracy? – chux - Reinstate Monica Aug 19 '23 at 12:37
  • @AdamBujak "I need to find the decimal exponent of a double." --> This deserves greater detail as to **why**. Even if you had an good way to do this, what higher level problem are you trying to solve? – chux - Reinstate Monica Aug 19 '23 at 15:32

3 Answers3

3

floor((log10(fabs(Value)))) is inadequate to compute the decimal exponent of a number.

Consider using IEEE-754 binary64 (“double precision”). Let Value be 0.09999999999999999167332731531132594682276248931884765625 = 9.999999999999999167332731531132594682276248931884765625•10−2, which is a representable value.

log10 of 0.09999999999999999167332731531132594682276248931884765625 is approximately ?−1.000000000000000036162279995748268157562864421389843161585017393347605917054938808868324457086236340. This number is not representable in binary64; the closest representable value is −1.

So a good log10 implementation will return −1 for input 0.09999999999999999167332731531132594682276248931884765625.

Then floor((log10(fabs(Value)))) produces −1, but the correct result would be −2.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Do you have any suggestions as to where I could find a better solution? – Adam Bujak Aug 18 '23 at 20:48
  • 2
    @AdamBujak: Converting floating-point to decimal efficiently is notoriously “hard.” References to some information are [here](https://stackoverflow.com/questions/7153979/algorithm-to-convert-an-ieee-754-double-to-a-string). I do not know to what extent reducing it to the sub-problem of obtaining only the exponent simplifies the task. – Eric Postpischil Aug 18 '23 at 20:55
0

Without math.h!

#include <stdio.h>

#define DIGITS 16

int decimal_exponent(double v)
{
  int len = DIGITS + 6, e;
  char buffer[len];

  sprintf(buffer, "%1.*e", DIGITS, v);
  sscanf(buffer + DIGITS + 3, "%i", &e);

  return e;
}

int main(void)
{
  double v = 0.09999999999999999167332731531132594682276248931884765625;
  printf("%i\n", decimal_exponent(v));
  return 0;
}

running:

$ ./exp 
-2

Yes, it's a joke

jjg
  • 907
  • 8
  • 18
0

When Value is near a power-of-10, the rounding in log10(Value) leads to values just smaller than a power-of-10 rounding up to the result to the next integer.

Code needs to use extended precession math somehow.

When ...printf() is well coded, we could use its extended math. Use enough precision to avoid rounding up to the next whole number.

#include <assert.h>
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int decimal_exponenet(double x) {
  assert(isfinite(x));
  //       -   d   .        ddddddd            e   -  expo  \0
  char buf[1 + 1 + 1 + (DBL_DECIMAL_DIG + 1) + 1 + 1 + 10 + 1];
  sprintf(buf, "%.*e", DBL_DECIMAL_DIG + 1, x);
  char *e = strchr(buf, 'e');
  assert(e);
  return atoi(e + 1);
}

For |Value| < 1.0, we have the additional problem that Value is not an exact power of 10. The above may still round in a undesired fashion for some extreme double encoding. More precision would help then. For common double, DBL_DECIMAL_DIG + 2 significant digits is sufficient.


Are there any edge/corner cases I am not considering

+/-0.0, +/-infinity, NAN deserve special attention. I would not expect subnormals to be an issue, but testing should include those.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256