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.
-
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: addingb
will not makea
larger and subtractingb
will not makea
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 offloor
.)Clever way of proving it: The code adds or subtracts multiples of
b
froma
untila
is in[-b/2,b/2)
, which you can view as adding or subtracting integers froma/b
untila/b
is in[-1/2,1/2)
, i.e. until(a/b+1/2)
(call itx
) is in[0,1)
. As you are only changing it by integers, the value ofx
does not changemod 1
, i.e. it goes to its remainder mod 1, which isx-floor(x)
. So the effective number of subtractions you make (which isc
) isfloor(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., whetherx
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. [Anda
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 asfloor(x+1)-1
which isfloor(x)
, and thaty
is actuallyx-1
, so(1+floor(y))=floor(x)
, and as for the conditionals:
when x≤0, it cannot be that (y≥0), soc
is just the first term which isfloor(x)
,
when 0 < x < 1, neither of the conditions holds, soc
is0
,
when 1 ≤ x, then only 0≤y, so c is just the second term which isfloor(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