Reputation: 21
EDIT see my comment below (the issue has not been resolved). Anyone who wants to try my code can do it here: http://tinker.io/e0676
EDIT2 I think I have found the problem: http://www.geocomputation.org/1999/076/gc_076.htm Will try to solve it somehow...
So here's the thing. If you have the area and perimeter of a polygon you can measure how close is a polygon to a circle by getting the result of area/(perimeter*perimeter) . I know how to measure the perimeter:
function getPolySize(arrP){ //the first and last point in arrP is the same!
var S = 0;
for(var i=1, l=arrP.length;i<l;i++)
S += Math.sqrt(Math.pow(arrP[i-1].x-arrP[i].x,2)+Math.pow(arrP[i-1].y-arrP[i].y,2));
return S;
}
And the Area:
function getPolyArea(arrP){
var A = 0;
for(var i=0,l=arrP.length-1;i<l;i++)
A += arrP[i].x*arrP[i+1].y - arrP[i+1].x*arrP[i].y;
A = A/2;
return Math.abs(A);
}
Everything works fine, but if I make a polygon that is as close to a circle as possible than I start to get weird results. I use this to make circle polygons:
var arrCircle = [];
function setPixel(x,y){
arrCircle.push({x:x,y:y});
}
function rasterCircle(xCenter, yCenter, radius)
{
var x, y, r2;
r2 = radius * radius;
for (x = -radius+1; x <= radius; x++){
y = Math.floor(Math.sqrt(r2 - x*x) + 0.5);
setPixel(xCenter + x, yCenter + y);
}
for (x = radius-1; x >= -radius; x--) {
y = Math.floor(Math.sqrt(r2 - x*x) + 0.5);
setPixel(xCenter + x, yCenter - y);
}
}
arrCircle.push(arrCircle[0]);
Since circle polygons with a larger radius are closer to the ideal circle than the ones with a smaller radius I figured the larger the circle the greater area/(perimeter*perimeter) should be, but that doesn't seem to be the case and I don't know why. The 'roundness' of an ideal circle is 1/4PI (0,07957747154594766788444188168626). I made a few tests with (circle like polygons) and these were my results:
radius 10 roundness 0.0760194950578244
radius 20 roundness 0.07542738445429042
radius 30 roundness 0.07549530319982335
radius 40 roundness 0.07472106160782283
radius 50 roundness 0.07490614579701928
radius 60 roundness 0.0750939048274632
radius 70 roundness 0.07502892726254591
radius 80 roundness 0.07512064515249058
radius 90 roundness 0.0752191417813967
So why does it decrease when it should increase? Can anyone help?
Upvotes: 2
Views: 375
Reputation: 7141
Edit: I realize this isn't exactly a complete explanation as to why, but I do think my current answer (as of 2013-03-30) gives some additional insight as to what is (or isn't) happening.
I'm beginning to think that the limit process of this sum isn't what we thought it was. The limit seems to go to something around 0.075{something.}
I would wager a guess that the utilization of rounding has something to do with it. That makes perfect sense for rasterizing, but you are using formula that get the exact areas, lengths along the edge of the circle, etc. of unrasterized polygons. Limits can be funny that way.
By way of comparison, I created a way of generating a polygon for the circle via repeated bisection. See: http://tinker.io/e0676/4 (also, the code below)
I also modified your getPolyArea
to use Big numbers in case that helped (for the original problem, it didn't).
//assumes one end of vector at origin
function findNormalizedbisector(x1,y1,x2,y2)
{
var xnew = (x1 + x2)/2;
var ynew = (y1 + y2)/2;
var normalizingfactor = Math.sqrt(xnew*xnew + ynew*ynew);
xnew = xnew/normalizingfactor;
ynew = ynew/normalizingfactor;
return {x:xnew, y:ynew};
}
function rasterCircle2(xCenter, yCenter, radius,levels){
var begin = [{x:1,y:0},{x:0,y:1},{x:-1,y:0},{x:0,y:-1},{x:1,y:0}];
var next;
var begin_length;
var i,j;
for (i=0;i<levels; i++){
next = [begin[0]];
for(j=1,begin_length = begin.length; j< begin_length; j++){
var nn1 = begin[j-1];
var nn2 = begin[j];
next.push(findNormalizedbisector(nn1.x,nn1.y,nn2.x,nn2.y));
next.push(nn2);
}
begin = next;
}
for(i=0,begin_length = begin.length; i< begin_length; i++){
var nn3 = begin[i];
nn3.x = nn3.x*radius + xCenter;
nn3.y = nn3.y*radius + yCenter;
}
return begin;
}
for(var i=1;i<10;i++){
var arr = rasterCircle2(200,200,800,i);
var round = getRoundness(arr);
document.write('levels_of_bisection: '+i+' num of points: '+arr.length+' roundness: '+round+'<br />');
}
The output of this plus my mods to your original are as follows. Notice that by way of bisection, we're finally seeing the number that we're expecting. Also note the output using the (mostly) original method with a radius of 8000
levels_of_bisection: 1 num of points: 9 roundness: 0.0754441738241592276887245725443626784735
levels_of_bisection: 2 num of points: 17 roundness: 0.0785521795644663802398108421050698627987
levels_of_bisection: 3 num of points: 33 roundness: 0.0793216436531942231002943881950341480514
levels_of_bisection: 4 num of points: 65 roundness: 0.0795135454101061996101090797300452576319
levels_of_bisection: 5 num of points: 129 roundness: 0.0795614919376626996487018238437628627337
levels_of_bisection: 6 num of points: 257 roundness: 0.0795734767642052437694275988417572320135
levels_of_bisection: 7 num of points: 513 roundness: 0.0795764728580322174704427590361452409546
levels_of_bisection: 8 num of points: 1025 roundness: 0.0795772218744387817478897891643047512062
levels_of_bisection: 9 num of points: 2049 roundness: 0.0795774091280998583674859904802485345865
radius: 8000 num of points: 32001 roundness: 0.0751003307245799769095159405113677586659
Upvotes: 1
Reputation: 21
I tried to do what Jayc suggested, but that only worked if I didn't use it with Big numbers and it only changed the last three decimal places. My numbers are incorrect at the third decimal place already.
I also tried to us big numbers as suggested above, but that didn't work either. I used https://github.com/MikeMcl/big.js with DP = 40 (decimal places). This lib can do sqrt unlike the one that was suggested above (the above could not do pow(0.5) either).
So here are the results:
radius 10 roundness 0.0760194950578243423134496712198170723197
radius 20 roundness 0.0754273844542903459173216205506653687584
radius 30 roundness 0.0754953031998234468551935482682038382823
radius 40 roundness 0.0747210616078230319421955905769628317386
radius 50 roundness 0.0749061457970194359965013537625645613175
radius 60 roundness 0.0750939048274633950478066316279606244677
radius 70 roundness 0.0750289272625461826452420199713700795491
radius 80 roundness 0.0751206451524908473183103264150172570221
radius 90 roundness 0.0752191417813970247297347505177938519628
And the new code:
var S = new Big(0);
var arr = [];
for(var i=1, l=arrP.length;i<l;i++){
var add = Math.pow(arrP[i-1].x-arrP[i].x,2)+Math.pow(arrP[i-1].y-arrP[i].y,2);
var x = new Big(add);
x = x.sqrt();
arr.push(x);
S = S.plus(x);
}
arr.sort(function(a,b){return a-b});
var c = new Big(0);
for(var i=0;i<arr.length;i++){
var y = arr[i].minus(c);
var t = S.plus(y);
c = t.minus(S).minus(y);
S = t;
}
I can't help but think that floating point isn't the issue here. I tried the tests with 1000(!) decimal places and got the exact same numbers as can be seen above (only a tad longer but same for the first 40 digits).
Something else is wrong, but what?
Upvotes: 0
Reputation: 37137
That's because javascript like many other languages has issues with floating points. That's a known issue. Your best solution is to round the numbers. Another option is to work with big numbers instead. Take a look at this library for precision: http://jsfromhell.com/classes/bignumber
If you are into math and want to understand it with more detail check this one out: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
Upvotes: 1