Recentemente, inserido num projecto extenso – e não quero falar sobre isso! – uma das fases consistia em dividir um círculo em fatias, de forma dinâmica. O grafismo é relativamente simples, basta usar uns lineTo
’s e uns curveTo
’s, tudo pintalgado com uns fills, e a coisa ia fluida. Quanto à matemática inerente, também me parecia bastante simples, basicamente é dividir um círculo completo pelo número de fatias.
O Actionscript, como quase todas as linguagens de programação, trabalha em radianos, o que acaba por ser, até, intuitivo. Aprende-se para aí no 7º ano que um círculo são 2 x PI, que corresponde aos 360 graus. Posto isto, o código era, aparentemente, trivial e croquetes:
// gráfico onde vamos desenhar as fatias var gfx:Graphics = container.graphics; // raio da tarte var raio:Number = oMeuRaio; // angulo de cada fatia var angulo:Number = (Math.PI * 2) / numFatias; // i é incrementado o angulo correspondente às fatias a cada passagem for(var i:Number = 0; i < (Math.PI * 2); i += angulo) { // mover para o centro do círculo gfx.moveTo(0, 0); // linha até ao limite da tarte gfx.lineTo(raio * Math.cos(i), raio * Math.sin(i)); }
Parece bastante simples e intuitivo, certo? Durante algumas horas, a coisa rolou maravilhosamente. Até que reparei que, nalgumas situações, era desenhada uma fatia extra por cima da primeira. Uma das situações onde isso acontecia era quando a quantidade de fatias era igual a seis. Alguns trace’s mais tarde, dei com o gato e, depois de praguejar durante alguns minutos contra a Adobe, a solução foi feita com um mínimo de esforço. Mas vamos ao gatinho primeiro…
Acontece que o Actionscript só tem um tipo de dados capaz de aguentar números com vírgula flutuante, o tipo Number
. E também acontece que o tipo Number
, tal como foi implementado, usa 64 bits, mas apenas 52 dos quais para os algarismos significativos, estando reservados 11 para um possível expoente (o outro bit que falta é o do sinal). Isto é, para inteiros é excelente e com um alcance fantástico, para números precisos… nem por isso. Além disso, e sendo PI uma constante estática da classe Math
, o número está guardado como Number
, pelo que a sua precisão é, evidentemente, limitada. Não esquecer que PI, até prova em contrário, é uma dízima infinita. Então, a representação de PI é 3,141592653589793. Reparem como é extremamente limitada.
No nosso caso em concreto, e para que se possa ver precisamente em que poço caí, a tabela de atribuições foi a seguinte, para 6 fatias (reparem na precisão dos números, devido ao tipo Number
):
Passagem | i += 1,0471975511965976 | i < 6,283185307179586 |
1 | 0 | sim |
2 | 1,0471975511965976 | sim |
3 | 2,0943951023931953 | sim |
4 | 3,141592653589793 | sim |
5 | 4,1887902047863905 | sim |
6 | 5,235987755982988 | sim |
7 | 6,283185307179585 | sim |
Ups! Quando tinha tudo para bater certo, a precisão do Number
fintou-nos! Mesmo no último algarismo significativo, pimba. Moral da história: não usarás o tipo Number
quando precisares de precisão. No caso concreto, a solução foi extremamente simples, como poderão ver abaixo. Para outros casos, o Actionscript pode ser um grande ninho de cobras. Beware.
// gráfico onde vamos desenhar as fatias var gfx:Graphics = container.graphics; // raio da tarte var raio:Number = oMeuRaio; // angulo de cada fatia var angulo:Number = (Math.PI * 2) / numFatias; // i é incrementado por número de fatias for(var i:int= 0; i < numFatias; i ++) { // mover para o centro do círculo gfx.moveTo(0, 0); // linha até ao limite da tarte gfx.lineTo(raio * Math.cos(i * angulo), raio * Math.sin(i * angulo)); }