Go Down

Topic: OBOE: Need help understanding, possible type conversion issue. (Read 284 times) previous topic - next topic

lamez

Hello,

I have an algorithm that its main purpose is decoding, converting, or what have you a string of uppercase letters (A-Z) into a number. This is almost like converting a base 26 number into base 10, however I believe that a base 26 number encoded would actually include numbers, I am working strictly with uppercase letters, I digress.


I have an implantation working in C++11, in fact most of the controller algorithm was a direct copy, with a slight change to the indexOf method.

The C++11 version works as I expect it to. So initially I thought the new indexOf method was incorrect, but after a simple test, it is working as expected. I have narrowed down the problem to a line in the convertTo10 where I am calling the pow function.

As you can see from the Serial.println()'s in the convertTo10 method, the output is printing exactly what we expect, 0 & 26.

However, the output of this function is 25. WHAT?

I have read the docs on the pow method from the Arduino lib, and it takes in two floats and returns a double. Even if the issue was a type conversion issue, converting a double to an int truncates the number, it does not round it. Well, this is from working with C++.

Any help with this issue would be much appreciated.

Code: (C++) [Select]

#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <inttypes.h>

#include <Arduino.h>
#include <avr/pgmspace.h>
#include <Wire.h>


const char _values[26] = {
    'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
    'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
    'U', 'V', 'W', 'X', 'Y', 'Z'
};

const int _val_len = 26;


int indexOf(char needle);
int convertTo10(String value, int len);

String x = "BA";
int len = 2;

void loop() { 
  Serial.print(x);
  Serial.print("=> ");
  Serial.println(convertTo10(x, 2));
  delay(50000);
}

int convertTo10(String value, int len) {
    int lastIndex = len - 1;
    double output = 0;
    int pos = 0;
    Serial.println();
    for (int i = lastIndex; i >= 0; i--) {
        int r = indexOf(value[i]);

        output += (r * pow((_val_len, pos));
        Serial.print("     output => ");
        Serial.println( output );
        pos++;
    }
    return floor(output);
}

int indexOf(char needle) {

int first = 0;
int last = _val_len - 1;
int mid; 

  do {   
    mid = (first + last) / 2;

    if (_values[mid] == needle)
      return mid;
    if (_values[mid] > needle)
      last = mid - 1;
    else
      first = mid + 1;
  } while (first <= last);

  return -1;
}



For reference here is a sample output:


BA =>
     output => 0.00
     output => 26.00
25

TonyWilk

First of all, your posted code does not compile.
the line:
    output += (r * pow((_val_len, pos));

gives:
    error: too few arguments to function 'double pow(double, double)'

and there's no setup() function.

.... so please post the WHOLE CODE, don't just chop out the bit you think the error is in.



Anyhow, if you used: return round(output);
you would get the expected result. Floating point, dontcha just love it. Within next-to-nothing of a nice integer result and floor() gives you the answer you didn't expect.

You would be better off staying with integers... at least you know what the result will be and it'll be a lot faster. Float calculations run like a dog (not our dog, which is a Border Collie, and she's like lightning going after a rabbit - but I digress).

Try:
Code: [Select]
int convertTo10(String value, int len) {
    int lastIndex = len - 1;
    int output = 0;
    int multiplier= 1;
    int pos = 0;
    Serial.println();
    for (int i = lastIndex; i >= 0; i--) {
        int r = indexOf(value[i]);
        Serial.print(value[i]);Serial.print("======");Serial.println(r);

        output += r * multiplier;
        multiplier= multiplier * 26;
        Serial.print("     output => ");
        Serial.println( output );
        pos++;
    }
    return output;
}


...and I hope you don't expect to ever convert a string of more than 3 characters.

The 4-character "CZZZ" would be 71,006 which cannot be held in an 'int' type. Using 'unsigned long' may be a good idea.


Yours,
  TonyWilk







PieterP

Also posted on Stack Exchange Arduino.

If you insist on doing this in the future, please post the link. Having two different discussions on the same problem isn't very efficient, and just wastes people's time.

Pieter

el_supremo

Don't send me technical questions via Private Message.


lamez

Also posted on Stack Exchange Arduino.

If you insist on doing this in the future, please post the link. Having two different discussions on the same problem isn't very efficient, and just wastes people's time.

Pieter
Right. I posted this to Stack Exchange as well as here and forgot to link back before I left work.

I am sorry about that, I was curious what the two different crowds had to say about this issue.

As expected a user on stack exchange posted a refactored version of method, which is great, but I think he missed the point. I want to know exactly what is happening here.



 
First of all, your posted code does not compile.
the line:
    output += (r * pow((_val_len, pos));

gives:
    error: too few arguments to function 'double pow(double, double)'

and there's no setup() function.

.... so please post the WHOLE CODE, don't just chop out the bit you think the error is in.

There was no need to post the whole code, it over 500 lines. However, I thought I could sneak that on by, but I guess I missed the setup method. You caught me :/

I don't know why that isn't compiling for you, I compiled the code with gnu using std 11. Also that error output doesn't really seem to make sense to me, seeing how it said two few arguments, but two arguments where provided.


Quote
Anyhow, if you used: return round(output);
you would get the expected result. Floating point, dontcha just love it. Within next-to-nothing of a nice integer result and floor() gives you the answer you didn't expect.
Right, it did not give me the expected output, but why?

Quote
You would be better off staying with integers... at least you know what the result will be and it'll be a lot faster. Float calculations run like a dog (not our dog, which is a Border Collie, and she's like lightning going after a rabbit - but I digress).

Correct, I agree about the float point operations being slow. That is what I initially started doing this with integers, but out of frustration I started playing around with data types just out of curiosity on how that would affect the output.   


Thanks for the help fellas.

el_supremo

Quote
Off By One Error
Thanks.
But that's AAWRDN (which is itself AAWRDN, in which case it is self-explanatory).

Pete
Don't send me technical questions via Private Message.

Delta_G

I have read the docs on the pow method from the Arduino lib, and it takes in two floats and returns a double. Even if the issue was a type conversion issue, converting a double to an int truncates the number, it does not round it. Well, this is from working with C++.
That truncation is the problem.  Floating point numbers are hardly ever exactly what you want them to be.  It's the nature of floating point numbers, they're approximations.  So when you do pow and you expect to get 26 but what the number really is may be 25.999999 or something.  When you print it it gets rounded because that's what print does with floating point, so you see 26.  But for your return value you use floor, so it gets floored to 25. 

The solution posted on SE is a much better way to do this.  Never use floating point math when you need integer values.  Start with the most significant digit in your letter-number and convert it to a number between 0 and 26 and store it as your result.  Then work through your letter-number working towards the least significant digit and for each one multiply your result by 26 and then add the value of the new digit. 
If at first you don't succeed, up - home - sudo - enter.

TonyWilk

...
Right, it did not give me the expected output, but why?
...
If you really want to know...

Decimal numbers are powers of 10, your "ABC" numbers are powers of 26 and binary are powers of 2. For straightforward integers, each base can represent the number exactly. But this is not the case for real numbers.

The decimal calculation 1/10 is exactly 0.1 whereas 1/3 is an approximation: 0.333333333 etc. The digits after the decimal point represent 1/10th, 1/100th and so on. In base 3 the calculation "1"/"101" is exactly "0.1" i.e one third.

We usually use floating point numbers to represent Decimal numbers, but the internal calculation is in binary, using binary fractions. so

Decimal 1 / 10 is Binary "1" / "1010" which is (according to this website: http://www.exploringbinary.com/binary-converter/)

   "0.00011001100110011001100110011"

and converting that back to Decimal gives you:

   0.0999999999999943156581139191

Even if the calculation looks like it should only involve integers, the floating point routines will introduce tiny binary fractional bits. So, whenever you can - avoid floating point in code.

Even if you really want a couple of decimal places, you can still do that in integers as 'fixed point' e.g.

Code: [Select]
// 5/4 a 2-place fixed point
long int q=5;
long int d=4;
long int integer, fraction;

  integer= (q*100) / d;     
  fraction= integer % 100;
  integer= integer / 100;
  Serial.print(integer);Serial.print(".");Serial.println(fraction);
 
}




Yours,
  TonyWilk

 



Go Up