I ran out of numbers.

Ha, so I thought that a Polargraph could be as big as your piece of string, well, that’s true, but there’s a significant caveat.  Because the string lengths are represented by variables of type long, it can never be longer than 2,147,483,647 steps.  One more step, and it rolls over to -2,147,483,648.

Now that’s pretty long.  With a PolargraphSD machine, that’s 63,761,390 mm, or a 63 metres diagonal distance.   Plenty room for expansion there.

Well, not quite, because to deal with strings that long, and machines that size, we need to have some headroom.  One of the core calculations that happens in Polargraph is working out the cartesian coordinates given the native coordinates:

float getCartesianXFP(float aPos, float bPos)  {
      float calcX = (sq(pageWidth) - sq(bPos) + sq(aPos)) 
        / (pageWidth*2);
      return calcX; 
}
float getCartesianYFP(float cX, float aPos) {
      float calcY = sqrt(sq(aPos)-sq(cX));
      return calcY;
}

So squaring (with the sq(…) function) produces a much larger number than is input, and it produces a number that is larger than can actually be expressed with a long (32 bit) number. Disconcertingly, sq(2147483647L) evaluates to 1.  1! That’s not helpful.

This calculation (sq(pageWidth)) is used a lot during any vector moves, and will be wrong if pageWidth or the target move positions are any larger than the square root of 2,147,483,647.  It’s 46,340 btw, which in steps is actually about 1.3m, or very very much less than 63 metres.  I’m slightly amazed that nobody (myself included) has not come across this problem already.  I have chased this issue down after helping a customer debug their envelope-pushing setup, and I’m a bit embarrassed to be honest, all my bold chat about “it’s as big as a piece of string”, well.

So, the good news is there’s a simple solution: cast to floats during the calculation.  In principle I may lose accuracy, but in practice I don’t believe it’s in a place where it is likely to be significant.  If it comes down to it, I could cast to use a long long, which is a 64 bit int type I only just discovered existed, but in a method that takes floats as parameters, and returns floats, I think it makes sense to treat the other numbers as floats too.

The fixed method looks like this:

float getCartesianXFP(float aPos, float bPos)  {
      float calcX = (sq((float)pageWidth) - sq(bPos) + sq(aPos)) 
        / ((float)pageWidth*2.0);
      return calcX; 
}

Note the casting to floats when referring to the variables that are actually longs.

The fixes (firmware 1.66) are in the usual places:

Though be aware that you only need bother update, if your machine is wider than 1.3m – there aren’t any other fixes in there.

– EDIT I introduced a new issue with 1.65.  1.66 (just now) should fix it.

3 thoughts on “I ran out of numbers.

  1. Actually I had this problem when I was using cheap geared motors. Because of the gears there where 64*64=4096 steps per revolution. It worked for single steps but microstepping was impossible. I already tried to search for the variables that were causing that error but without success.
    Its great to hear that you fixed this!

    • Aha! Yes, I suppose I only came across the issue because the polargraphSD uses 8x microstepping so 3200 steps per rev. If I’d gone for full 16x stepping (which the polarshield is happy to do), or used the geared steppers then it would have shown up even earlier I guess. It’s really a symptom of an informal education in C and Arduino.

  2. Since your initial calculation of pageWidth is static you could also calculate a float variation at the same time and use that in getCartesianXFP() and save the overhead of casting every time the function is called.

    long pageWidth = machineWidth * stepsPerMM;
    float pageWidth_fp = (float)pageWidth;

    float getCartesianXFP(float aPos, float bPos) {
    float calcX = (sq(pageWidth_fp – sq(bPos) + sq(aPos))
    / (pageWidth_fp * 2.0);
    return calcX;
    }

    You could also pick up some more performance by eliminating the (pageWidth_fp * 2.0) calculation since it is always the same value by assigning it to a static variable inside the function.

    float getCartesianXFP(float aPos, float bPos) {
    static float twoTimesPageWidth = pageWidth_fp * 2.0;
    float calcX = (sq(pageWidth_fp – sq(bPos) + sq(aPos))
    / twoTimesPageWidth;
    return calcX;
    }

Leave a Reply