0

I am trying to convert a floating point number to an integer, but it seems like the conversion is not working properly.

I am using the math library to calculate the power of 10 based on the number of varargs, where the first argument is the number of arguments followed by the varargs.

int charsToIntValue(int argc, ...) {
  int multiplier = (int) pow(10, argc - 1);
  va_list argp;
  va_start(argp, argc);
  Serial.println(multiplier);
  ...
  va_end(argp);
}

For some reason, multiplier is always stored as 1 less than the actual result. e.g. If charsToIntValue(4, 1, 2, 3, 4) is executed, the multiplier is stored as 999 rather than 1000. However, when it is printed as a float, it does show 1000.00. And whenever I change it to a constant ((int) pow(10, 3)) it works fine, returning 1000.

I've tired storing the value of argc into a local variable but that didn't seem to change anything.

Full project is here for context: https://github.com/Pyrodron/charger-destination-board

File in question is charger-destination-board.ino; lines 32 to 51

3
  • 1
    A minimal test setup around your charsToIntValue doesn't show me what you're talking about. I don't think this has anything strictly to do with variadic function support. Nor Arduino. I will just add that pow(10, argc - 1); could be better written as a small function that takes an int, returns an int, and consists of a switch statement with no float at all.
    – timemage
    Commented Feb 2, 2021 at 0:03
  • 2
    I'm currently not sure, if pow() returns a float. If yes, that that behavior seems normal. Floats can only save an aproximation of the wanted number. The actual float value, that is calculated might be something like 999.99. When you them convert to an int, the digits behind the decimal point get thrown away, leaving 999. Thats something, that you would need to accept, when you want to work with floats. And thats one reason, why it is often better, to just use an integer representation.
    – chrisl
    Commented Feb 2, 2021 at 7:21
  • 1
    @timemage I took inspiration from your comment and implemented my own power function rather than using the math.h library. It works now! Commented Feb 2, 2021 at 14:25

2 Answers 2

2

With some inspiration from timemage's comment, I decided in the long run to implement my own power function that takes two int arguments and returns an int. Rather than use the pow function in the <math.h> library which returns a double when I only need an int.

int power(int x, int y) {
  return y == 0 ? 1 : x * power(x, y - 1);
}

Now the program works as intended!

3
  • 1
    I'd had some doubt about this actually solved the problem, because I'd tested the handful of exponent values you were using to see if the pow() happened to produce the values you wanted. Turns out avr-gcc will use higher precision if the optimizer can determine at compile time irrespective of whether it's doing this is a constexper context, meaning so much as having an argument something volatile qualified will cause pow() to evaluate at lower precision. Just more reason to not use float on an AVR.
    – timemage
    Commented Feb 2, 2021 at 16:55
  • 1
    When the optimizer kicked in pow(10, 4)'s result had significand field 0b00111000011111111111110 and where the optimizer couldn't determine the exponent at compile time a significand field of 0b00111000100000000000000. The former converts to int with the expected (for certain values of "expected") 10000 while the latter produces a value closer to 9984. Something also you should be aware of is that in avr-gcc, there really is no double. It's a distinct type for overload purposes, but it's implemented as single precision float.
    – timemage
    Commented Feb 2, 2021 at 16:59
  • @timemage all helpful information. Thank you Commented Feb 4, 2021 at 0:23
0

As explained by @chrisl in a comment, the issue is that pow() does not return an exact result, only an approximation. It works fine with compile-time constants because in this case it is evaluated at compile time, by the compiler, on you PC.

Simple solution: replace the cast to int with round():

int multiplier = round(pow(10, argc - 1));

This works, but is awfully inefficient, as pow() involves the computation of both an exponential and a logarithm, both very expensive on an AVR-based Arduino. The int-only function in your own answer is certainly better. However, it is generally advised to avoid recursion on low-memory Arduinos like the Uno. Prefer an iterative algorithm if you can.

Also, the complexity of you int-only function is O(y). There is a well-known algorithm for integer powers that goes like O(log(y)):

int power(int x, int y)
{
    int z = 1;  // Invariant: result = x^y * z
    while (y) {
        if (y & 1) z *= x;
        y >>= 1;
        x *= x;
    }
    return z;
}

Maybe I am nitpicking, as your y cannot go beyond 5 without overflowing the result...

1
  • "works fine with compile-time constants because" as I said in the the comments on the O.P.'s own answer, it's actually more complicated than that. It has nothing to do with whether or not they're technically "constant". You can elicit the difference in this behaviour without crossing between constant expressions and non-constant expressions. The standard makes provisions for differences in precision between the two cases. So that is fin. But, the fact that it will give two completely different results outside the constexpr context is probably a bug.
    – timemage
    Commented Feb 2, 2021 at 17:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.