Monday, March 7, 2011

Turn while loop into math equation?

I have two simple while loops in my program that I feel ought to be math equations, but I'm struggling to convert them:

float a = someValue;
int b = someOtherValue;
int c = 0;

while (a <= -b / 2) {
    c--;
    a += b;
}
while (a >= b / 2) {
    c++;
    a -= b;
}

This code works as-is, but I feel it could be simplified into math equations. The idea here being that this code is taking an offset (someValue) and adjusting a coordinate (c) to minimize the distance from the center of a tile (of size someOtherValue). Any help would be appreciated.

From stackoverflow
  • I think you want something like this:

    c = ((int) a + b / 2 * sign(a)) / b
    

    That should match your loops except for certain cases where b is odd because the range from -b/2 to b/2 is smaller than b when b is odd.

    Ed Swangren : "sin", not "sign", but yes, you want a sin wave function.
    Greg Hewgill : I think Dave really means sign(), defined by: int sign(x) { return x > 0 ? 1 : x < 0 ? -1 : 0; }
    ShreevatsaR : Not correct: for a trivial example, take a = b = 10, then neither loop ever runs at all (so c=0) but this answer will say c=1.
    Dave L. : Shreevatsar: That's not true. If a = b = 10, then a >= b/2, so the later loop will run once, and c = 1.
    ShreevatsaR : Oh sorry, I wasn't thinking clearly. For a=b=10, this says c=((int) 10+5)/10, which is actually 1.5 :-) But if a and b are ints, so that "/" means integer division, then for positive a, it is floor(a+b/2)/b, so right, and for negative a it is ceil(a-b/2)/b=floor((a+b/2)/b) *except* when b|(a-b/2).
    ShreevatsaR : That is, pick negative a such that it is b/2 mod b, e.g. b=10 and a=-5. Then c=-1+1=0, but ((int)-5-5)/10 is -1. But yeah, this is correct except when a is negative and b divides a-b/2; sorry for my first comment.
    ShreevatsaR : BTW, if I may ask: how did you think of this? It seems something quite clever, and completely different from the others.
  • Assuming b is positive, abs(c) = floor((abs(a) - b/2) / b). Then, apply sign of a to c.

  • c = (int)((a - (b / 2)) / b + 1);
    a -= c * b;
    

    Test case at http://pastebin.com/m1034e639

    Sydius : It seems to work for positive a, but not negative.
    Sydius : Using floor instead of dropping the decimal portion by converting it to an integer makes it work for negative values.
  • It can be proved that the following is correct:

    c = floor((a+b/2)/b)
    a = a - c*b
    

    Note that floor means round down, towards negative infinity: not towards 0. (E.g. floor(-3.1)=-4. The floor() library functions will do this; just be sure not to just cast to int, which will usually round towards 0 instead.)

    Presumably b is strictly positive, because otherwise neither loop will never terminate: adding b will not make a larger and subtracting b will not make a smaller. With that assumption, we can prove that the above code works. (And paranoidgeek's code is also almost correct, except that it uses a cast to int instead of floor.)

    Clever way of proving it: The code adds or subtracts multiples of b from a until a is in [-b/2,b/2), which you can view as adding or subtracting integers from a/b until a/b is in [-1/2,1/2), i.e. until (a/b+1/2) (call it x) is in [0,1). As you are only changing it by integers, the value of x does not change mod 1, i.e. it goes to its remainder mod 1, which is x-floor(x). So the effective number of subtractions you make (which is c) is floor(x).

    Tedious way of proving it:

    At the end of the first loop, the value of c is the negative of the number of times the loop runs, i.e.:

    • 0 if: a > -b/2 <=> a+b/2 > 0
    • -1 if: -b/2 ≥ a > -3b/2 <=> 0 ≥ a+b/2 > -b <=> 0 ≥ x > -1
    • -2 if: -3b/2 ≥ a > -5b/2 <=> -b ≥ a+b/2 > -2b <=> -1 ≥ x > -2 etc.,

    where x = (a+b/2)/b, so c is: 0 if x>0 and "ceiling(x)-1" otherwise. If the first loop ran at all, then it was ≤ -b/2 just before the last time the loop was executed, so it is ≤ -b/2+b now, i.e. ≤ b/2. According as whether it is exactly b/2 or not (i.e., whether x when you started was exactly a non-positive integer or not), the second loop runs exactly 1 time or 0, and c is either ceiling(x) or ceiling(x)-1. So that solves it for the case when the first loop did run.

    If the first loop didn't run, then the value of c at the end of the second loop is:

    • 0 if: a < b/2 <=> a-b/2 < 0
    • 1 if: b/2 ≤ a < 3b/2 <=> 0 ≤ a-b/2 < b <=> 0 ≤ y < 1
    • 2 if: 3b/2 ≤ a < 5b/2 <=> b ≤ a-b/2 < 2b <=> 1 ≤ y < 2, etc.,

    where y = (a-b/2)/b, so c is: 0 if y<0 and 1+floor(y) otherwise. [And a now is certainly < b/2 and ≥ -b/2.]

    So you can write an expression for c as:

    x = (a+b/2)/b
    y = (a-b/2)/b
    c = (x≤0)*(ceiling(x) - 1 + (x is integer))
       +(y≥0)*(1 + floor(y))
    

    Of course, next you notice that (ceiling(x)-1+(x is integer)) is same as floor(x+1)-1 which is floor(x), and that y is actually x-1, so (1+floor(y))=floor(x), and as for the conditionals:
    when x≤0, it cannot be that (y≥0), so c is just the first term which is floor(x),
    when 0 < x < 1, neither of the conditions holds, so c is 0,
    when 1 ≤ x, then only 0≤y, so c is just the second term which is floor(x) again. So c = floor(x) in all cases.

    Sydius : Excellent answer. It works perfectly, though I will need to read it a few times to understand it fully.
    Georg : thanks for that long explanation!
    ShreevatsaR : My pleasure :) Actually I guess it could be a bit simpler... I just wrote it the way I first worked it out. If something is confusing, please point it out and I'll take another look at it.

0 comments:

Post a Comment