Selección 1

En este capítulo se estudian los operadores relacionales, los operadores lógicos, la estructura condicional if (si) y se las emplea en la resolución de problemas que implican la toma de decisiones, además, se complementa el estudio de las funciones introduciendo los parámetros por defecto y los parámetros nombrados.

Operadores relacionales

Los operadores relacionales son empleados en la escritura de condiciones en programación. Una condición es toda sentencia de la cual se puede determinar su veracidad (true) o falsedad (false).

Los operadores relacionales comparan dos valores (escritos a ambos lados del operador) y devuelven siempre un valor booleano (true o false). Javascript cuenta con los siguientes operadores relacionales:

Operadores Relacionales
OperadorDescripción
==Igual a
===Igual y del mismo tipo que
!=Diferente de
!==Diferente o de otro tipo que
>Mayor a
<Menor a
>=Mayor o igual a
<=Menor o igual a

Por defecto, JavaScript, realiza las conversiones de tipos que sean necesarias, antes de comparar los valores.

Por ejemplo, se pueden comparar cadenas con números:

"50.34">5.554
30.45<"3.43"
12>="15"
"23"<=64

En estas comparaciones, JavaScript, convierte primero las cadenas en números y luego compara los valores convertidos. No obstante, no es una buena práctica de programación el mezclar diferentes tipos de datos, por lo que se aconseja no hacerlo.

La diferencia entre los operadores == y != y los operadores === y !==, radica en que los últimos no realizan ningun tipo de conversión, lo que es más seguro desde el punto de vista de programación y la razón por la cual se recomienda su uso.

Por ejemplo, en las siguientes comparaciones, se obtienen resultados distintos con los dos tipos de operadores:

"23" == 23
"23" === 23
"68.5" != 68.5
"68.5" !== 68.5

Operadores lógicos

Los operadores lógicos comparan valores booleanos (o expresiones que develven resultados booleanos o que pueden ser interpretados como valores booleanos). Conjuntamente los operadores relacionales, permiten escribir expresiones lógicas complejas.

Javascript cuenta con los siguientes operadores lógicos:

Operadores Lógicos
OperadorDescripción
!Negación
&&Y lógico
||O lógico

El operador de negación: !, cambia el valor booleano o el resultado de una expresión que devuelve un valor booleano (o un valor que puede ser interpretado como un valor booleano) de verdadero a falso y viceversa. El operador "Y": &&, devuelve verdadero sólo si los dos valores que compara son verdaderos, en cualquier otro caso devuelve falso. El operador "O" lógico: ||, devuelve falso sólo si los dos valores que compara son falsos, en cualquier otro caso devuelve verdadero.

JavaScript interpreta como valores falsos: el cero (0), el texto vacío (""), el valor nulo (null) y el valor undefined e interperta como verdaderos cualquier cadena no vacía y cualquier valor no nulo o definido.

Por ejemplo, las siguientes instrucciones devuelven la negación de algunos de estos valores:

!""
!"cadena"
!undefined
!null
!54.23

Evaluación en corto circuito

En Javascript, al igual que en la mayoría de los lenguajes de programación, los operadores lógicos || e && se evalúan en cortocircuito, esto es, si con el primer valor se puede determinar (sin lugar a dudas) el resultado de la expresión, entonces el resultado es devuelto directamente, sin tomar en cuenta (sin evaluar) el segundo valor.

En el caso del operador &&, se sabe que el resultado es falso si el primer valor es falso (porque falso && cualquier otro valor es siempre falso). En el caso del operador ||, se sabe que el resultado es verdadero si el primer valor es verdadero (porque verdadero || cualquier otro valor es siempre verdadero).

Así, en los siguientes ejemplos, todas las expresiones devuelven un resultado booleano (no un error), a pesar de que (en todos los casos) los segundos términos son erróneos: son variables y funciones inexistentes:

false && cos(3.2)
false && miVariable
true || tangente(6.43)
true || otraVariable

En JavaScript, el resultado de un operador lógico no siempre es un valor booleano (true/false) sino el valor con el cual se determina la veracidad o falsedad de la expresión.

Así, el operador "Y" (&&) devuelve el primer término, si el mismo equivale a falso y el segundo en caso contrario, mientras que el operador "O" (||) devuelve el primer término si es equivalente a verdadero y el segundo en caso contrario. Tal como se puede ver en los siguientes ejemplos:

"" && 3 > 2
undefined && true
null && "Hola"
Math.sin(0) && Math.cos(5.32)
Math.cos(0) || true
"Javascript" || false
"Aceptado" || "Rechazado"
Math.sqrt(25) || Math.cbrt(27)

Y las siguientes devuelven el segundo valor, pues con el primero no es posible determinar la veracidad o falsedad de la expresión:

true && "Resultado"
"Primero" && "Segundo"
1 && Math.sin(5.3)
false || "Resultado"
undefined || "Valor por defecto"
"" || "Rechazado"
"Aceptado" && "Rechazado"
0 || 2**5
45<23 || Math.sqrt(2)

Ejemplos

Número par

Como primer ejemplo, se elaborará una función que, haciendo uso de una expresión lógica, determine si un número es (true) o no (false) par:

Como se sabe, un número es divisible entre otro si el residuo de su división es cero y un número (n) es par si es divisible entre 2, es decir si se cumple que: n%2===0. Como el resultado de toda expresión relacional es siempre un valor booleano (true o false), el problema se resuelve simplemente devolviendo el resultado de la expresión:

var esPar = n => n%2 === 0;

Llamando a la función con los números 124, 311 y 0, se obtiene:

esPar(124)
esPar(311)
esPar(0)

Qué son resultados correctos.

Número entero

Un número es entero si no tiene parte fraccionaria y un número no tiene parte fraccionaria si el residuo de su división entre 1 es cero, entonces para determinar si un número (n) es (true) o no (false) un entero, simplemente se verifica si es divisible entre 1: n%1===0:

var esEntero = function(n) {
return n%1 === 0;
};

LLamando a la función desde los números 124, 911, 124.34, -457 y -93.672, se obtiene:

esEntero(124)
esEntero(911)
esEntero(124.24)
esEntero(-457)
esEntero(-93.672)

Que son los resultados correctos.

Número natural

Un número (n) es natural si es entero y positivo. La mayoría de las definiciones incluyen el número 0 en este conjunto, siendo esa la definición que se adopta en la asignatura.

Al igual que en los otros casos, el problema es directamente el resultado de una expresión lógica, donde se determina si el número es divisible entre 1 (si es entero) y si es positivo (mayor o igual a cero): n%1===0 && n>=0:

function esNatural(n) {
return n%1 === 0 && n >= 0;
};

Haciendo correr el programa con 27, 0, -45, 23.45 y -92.14, se obtiene:

esNatural(27)
esNatural(0)
esNatural(-45)
esNatural(23.45)
esNatural(-92.14)

Que son resultados correctos.

Parámetros

Los parámetros empleados hasta el momento, en todas las funciones elaboradas, son parámetros por valor. Como su nombre sugiere, estos parámetros sólo guardan el valor que se les manda, sin importar que dicho valor sea literal, esté guardado en una variable o sea el resultado de una función o expresión.

Por ejemplo, dada la función:

var sum2 = (x, y) => x+y;

Si es llamada, guardando previamente los valores a sumar en las variables "a" y "b":

var a = 7, b = 8;
sum2(a, b)

La función sum2, no guarda las variables "a" y "b" en las variables "x" y "y", sino únicamente sus valores (los números 7 y 8).

Las variables originales permanecen inalteradas porque, como se dijo, la función no recibe las variables, sino únicamente sus valores. Como se puede comprobar en la siguiente función, que recibe dos números en los parámetros "c" y "d", estos parámetros son modificados dentro de la función, pero, como se puede ver, las variables externas permanecen inalteradas.

var c = 5, d = 7;
print(c, d);
function fun1(c, d) {
c = c*3;
d = d*4;
return c+d;
}
print(fun1(c, d));
print(c, d)

Los parámetros pueden ser también parámetros por defecto, que son parámetros a los que se les asignan valores iniciales. Con estos parámetros, si no se mandan datos para uno o más de los parámetros, la función emplea los valores por defecto asignados.

Por ejemplo, la siguiente función calcula la suma de hasta 3 números, pero todos los parámetros tienen valores por defecto iguales a 0:

var suma = (a=0, b=0, c=0) => a+b+c;

Si la función no recibe uno o más de los 3 valores que requiere, emplea los valores por defecto (0). En este caso cero es un valor adecuado, porque al ser el valor neutro de la suma no altera el resultado. Así, si la función no recibe "c", devuelve el resultado de sumar "a" y "b", porque al ser "c" 0, no afecta al resultado: a+b+0=a+b.

En los siguientes ejemplos, en la primera llamada que se mandan los tres valores (a=1, b=2 y c=3). En la segunda se mandan dos valores (a=1, b=2), por lo tanto "c" es 0. En la tercera llamada se manda un valor (a=1) por lo tanto "b" y "c" son 0 y en la última llamada no se manda ningún valor, por lo tanto "a", "b" y "c" son 0:

suma(1, 2, 3)
suma(1, 2)
suma(1)
suma()

Como se puede ver, en todos los casos la función devuelve el resultado correcto, inclusive cuando no se manda ningún valor.

Normalmente, los parámetros por defecto están al final de la lista, sin embargo, en JavaScript los parámetros por defecto pueden estar en cualquier posición (no siempre al final), no obstante, para que un parámetro que no está al final emplee su valor por defecto, se debe mandar (en esa posición) undefined. Por ejemplo, para que el segundo parámetro de suma emplee su valor por defecto, se escribe:

suma(12, undefined, 45)

En este ejemplo "a" es 12, "b" es 0 y "c" es 45.

Aunque las funciones en JavaScript no admiten propiamente parámetros nombrados, pueden ser emulados recurriendo a los objetos literales, de acuerdo a la siguiente sintaxis:

({par1[=val1], par2[=val2], ..., parn[=valn]}={})

Donde, par1, par2, etc., son los nombres de los parámetros. Como se puede ver, los parámetros nombrados también pueden tener valores iniciales por defecto. Los parámetros nombrados se caracterizan porque, como su nombre sugiere, los datos deben ser mandados incluyendo el nombre del parámetro. Como los datos se mandan conjuntamente el nombre del parámetro, pueden ser mandados en cualquier orden (a diferencia de los parámetros por valor y por defecto, que deben ser mandados en el orden en que fueron declarados).

La igualdad y las llaves vacías escritas al final de la lista de parámetros: ={}, sólo son necesarias para aquellos casos en los que la función puede ser llamada si enviar ningún dato. Si se tiene la certeza de que la función recibirá, siempre, por lo menos un dato (inclusive si ese dato es un objeto vacío: {}) entonces la igualdad y las llaves pueden ser omitidas.

Los parámetros nombrados son particularmente útiles cuando la función consta de 3 o más parámetros, porque entonces es más fácil recordar el nombre de los parámetros, que la posición en la que fueron declarados.

La función suma, implementada con parámetros nombrados, es:

var suma_1 = ({a=0, b=0, c=0}={}) => a+b+c;

La cual puede ser llamada escribiendo los parámetros en desorden:

suma_1({c:3, b:2, a:1})
suma_1({b:2, a:1})
suma_1({c:3})
suma_1()

Si no se emplea ={}:

var suma_2 = ({a=0, b=0, c=0}) => a+b+c;

La función devuelve los mismos resultados, excepto en la última llamada (donde no se manda ningún dato, ni siquiera un objeto vacío):

suma_2({c:3, b:2, a:1})
suma_2({b:2, a:1})
suma_2({c:3})
suma_2()

Por eso, se recomienda igualar (siempre) la lista de parámetros nombrados al objeto vacío: ={}.

En el siguiente ejemplo, empleando parámetros nombrados, se programa la función que permite calcular la distancia (d) que alcanza un proyectil disparado con una velocidad inicial \(v_0\) (en m/s), con un ángulo de disparo θ (en grados), con la ecuación del movimiento parabólico:

\[ d = \dfrac{v^2_0 \sin(2\theta)}{2g} \]

En este caso es conveniente declarar como parámetro nombrado, con un valor por defecto, la aceleración de la gravedad (g): 9.80665 (m/s2), porque casi siempre se emplea ese valor (la gravedad estándar en la tierra) en los cálculos:

var alcance = function({v0, theta, g=9.80665}={}) {
theta = Math.toRad(theta);
return v0**2*Math.sin(2*theta)/(2*g);
};

En esta función, como se puede ver, primero se transforma el ángulo de grados a radianes (con toRad), porque las funciones trigonométricas trabajan con ángulos en radianes, no en grados.

Llamando a la función, para calcular la distancia que alcanza un proyectil disparado con una velocidad inicial de 150 m/s y un ángulo de 30 grados, se obtiene:

alcance({theta:30, v0:150})

Por lo tanto, el proyectil alcanza una distancia de 993.5 m.

Como se puede ver en este ejemplo, se han mandado los datos a la inversa de como fueron declarados (se ha mandado primero theta y luego v0), lo que constituye, como se dijo, la principal ventaja de los parámetros nombrados: pueden ser mandados en cualquier orden.

Si se requiere un resultado más preciso, se debe mandar también la aceleración local de la gravedad. Así un valor más adecuado para Sucre, es: g=9.77721 m/s2):

alcance({g:9.77721, theta:30, v0:150})

Que es casi 3 metros más que el resultado obtenido con la aceleración estándar.

La estructura if (si)

If es la estructura selectiva por excelencia, su lógica, en forma de diagrama de flujo, es:

gojsGraph({divi, modelo:{ "class": "go.GraphLinksModel", "linkFromPortIdProperty": "fromPort", "linkToPortIdProperty": "toPort", "nodeDataArray": [ {"category":"Connector", "text":"...", "key":-8, "loc":"-184 -400"},{"category":"Conditional", "text":"condición", "key":-4, "loc":"-184 -341"},{"category":"Process", "text":"instrucciones A", "key":-2, "loc":"-71 -280"},{"category":"Process", "text":"instrucciones B", "key":-5, "loc":"-287 -280"},{"category":"Connector", "text":"...", "key":-6, "loc":"-178 -224"} ], "linkDataArray": [ {"from":-8, "to":-4, "fromPort":"B", "toPort":"T", "points":[-184,-385.3042461927547,-184,-375.3042461927547,-184,-373.16880171576213,-184,-373.16880171576213,-184,-371.03335723876955,-184,-361.03335723876955]},{"from":-4, "to":-2, "fromPort":"R", "toPort":"T", "visible":true, "points":[-119.50294494628906,-341,-109.50294494628906,-341,-71,-341,-71,-324.13333930969236,-71,-307.2666786193848,-71,-297.2666786193848], "text":"Si"},{"from":-4, "to":-5, "fromPort":"L", "toPort":"T", "visible":true, "points":[-248.49705505371094,-341,-258.49705505371094,-341,-287,-341,-287,-324.13333930969236,-287,-307.2666786193848,-287,-297.2666786193848], "text":"No"},{"from":-5, "to":-6, "fromPort":"B", "toPort":"L", "points":[-287,-262.7333213806152,-287,-252.73332138061522,-287,-224,-244.84787690362265,-224,-202.6957538072453,-224,-192.6957538072453,-224]},{"from":-2, "to":-6, "fromPort":"B", "toPort":"R", "points":[-71,-262.7333213806152,-71,-252.73332138061522,-71,-224,-112.15212309637735,-224,-153.3042461927547,-224,-163.3042461927547,-224]} ]}})

Es decir que if ejecuta las instrucciones A si la condición se cumple (si es verdadera), caso contrario (else), si es falsa, ejecuta las instrucciones B. Luego el programa continua con las instrucciones restantes.

En JavaScript, la sintaxis de esta estructura, es:

if (condición) { instrucciones A } else { instrucciones B }

Si en lugar de dos o más instrucciones sólo existe una, las llaves no son necesarias pero el escribirlas no es un error:

if (condición) instrucción_1; else instrucción_2;

El caso contrario (las instrucciones B) es opcional y la estructura puede tener únicamente el caso verdadero (las instrucciones A):

gojsGraph({divi, modelo:{ "class": "go.GraphLinksModel", "linkFromPortIdProperty": "fromPort", "linkToPortIdProperty": "toPort", "nodeDataArray": [ {"category":"Connector", "text":"...", "key":-8, "loc":"-184 -400"},{"category":"Conditional", "text":"condición", "key":-4, "loc":"-184 -341"},{"category":"Process", "text":"instrucciones A", "key":-2, "loc":"-71 -280"},{"category":"Connector", "text":"...", "key":-6, "loc":"-184 -224"} ], "linkDataArray": [ {"from":-8, "to":-4, "fromPort":"B", "toPort":"T", "points":[-184,-385.3042461927547,-184,-375.3042461927547,-184,-373.16880171576213,-184,-373.16880171576213,-184,-371.03335723876955,-184,-361.03335723876955]},{"from":-4, "to":-2, "fromPort":"R", "toPort":"T", "visible":true, "points":[-119.50294494628906,-341,-109.50294494628906,-341,-71,-341,-71,-324.13333930969236,-71,-307.2666786193848,-71,-297.2666786193848], "text":"Si"},{"from":-2, "to":-6, "fromPort":"B", "toPort":"R", "points":[-71,-262.7333213806152,-71,-252.73332138061522,-71,-224,-115.15212309637735,-224,-159.3042461927547,-224,-169.3042461927547,-224]},{"from":-4, "to":-6, "fromPort":"B", "toPort":"T", "visible":true, "points":[-184,-320.9666427612305,-184,-310.9666427612305,-184,-279.83119828423787,-184,-279.83119828423787,-184,-248.6957538072453,-184,-238.6957538072453], "text":"No"} ]}})

En cuyo caso, la sintaxis, es:

if (condición) { instrucciones A }

O si sólo existe una instrucción:

if (condición) instrucción_1;

Como se dijo, en JavaScript, si una instrucción termina con un salto de línea, el punto y coma al final de la misma no es imprescindible, sin embargo, para adquirir buenas costumbres y reducir errores, en la asignatura se debe escribir siempre un punto y coma al final de cada instrucción.

Sin embargo, cuando la instrucción está en la última línea de la calculadora JavaScript, sólo se debe escribir el punto y coma cuando no se quiere mostrar el resultado de esa instrucción.

Ejemplos

Par, impar, cero

En este ejemplo se elabora una función que determina si un número entero es par, impar o cero, devolviendo el texto "par", si es par; "impar", si es impar y "cero", si es cero.

Como casi siempre ocurre en programación, el problema puede ser resuelto de diferentes formas, así, se puede verificar primero si el número es cero y de ser así el resultado es "cero". Sino (caso contrario) se puede verificar si es par y de ser así el resultado es "par", caso contrario el resultado es "impar", porque si el número no es cero ni par (por lógica) sólo puede ser impar (el caso por defecto).

Como ya se vio previamente, para determinar si un número entero es par, simplemente se verifica si el residuo de su división entre 2 sea igual a 0.

Igualmente, se puede verificar primero si el número es impar y luego si es cero, siendo el caso por defecto par.

Sin embargo, no se puede verificar primero si el número es par, porque el residuo de 0 entre 2 es también 0 (al igual que ocurre con un número par). En consecuencia, si se verificaría primero si el número es par, el algoritmo devolvería (erróneamente) "par" cuando el número es 0.

El algoritmo, en forma de diagrama de flujo, donde se verifica primero si el número es cero y luego si es par, es:

gojsGraph({divi, modelo:{ "class": "go.GraphLinksModel", "linkFromPortIdProperty": "fromPort", "linkToPortIdProperty": "toPort", "nodeDataArray": [ {"category":"Conditional", "text":"n==0", "key":-4, "loc":"-131 -319"}, {"category":"Start", "text":"parImparCero", "key":-1, "loc":"-131 -428"}, {"category":"Input", "text":"número entero: n", "key":-3, "loc":"-131 -377"}, {"category":"Output", "text":"\"cero\"", "key":-5, "loc":"-35 -236"}, {"category":"Conditional", "text":"n%2==0", "key":-6, "loc":"-224 -262"}, {"category":"Output", "text":"\"par\"", "key":-7, "loc":"-130 -195"}, {"category":"Output", "text":"\"impar\"", "key":-8, "loc":"-311 -197"}, {"category":"Start", "text":"fin", "key":-9, "loc":"-130 -109"} ], "linkDataArray": [ {"from":-1, "to":-3, "fromPort":"B", "toPort":"T", "points":[-131,-412.7333213806152,-131,-402.7333213806152,-131,-402,-131,-402,-131,-401.2666786193848,-131,-391.2666786193848]}, {"from":-3, "to":-4, "fromPort":"B", "toPort":"T", "points":[-131,-362.7333213806152,-131,-352.7333213806152,-131,-350.88333930969236,-131,-350.88333930969236,-131,-349.03335723876955,-131,-339.03335723876955]}, {"from":-4, "to":-5, "fromPort":"R", "toPort":"T", "visible":true, "points":[-94.84066772460938,-319,-84.84066772460938,-319,-35,-319,-35,-291.30556551615393,-35,-263.6111310323079,-35,-253.61113103230792], "text":"Si"}, {"from":-4, "to":-6, "fromPort":"L", "toPort":"T", "visible":true, "points":[-167.15933227539062,-319,-177.15933227539062,-319,-224,-319,-224,-305.5166786193848,-224,-292.03335723876955,-224,-282.03335723876955], "text":"No"}, {"from":-6, "to":-7, "fromPort":"R", "toPort":"T", "visible":true, "points":[-167.81512451171875,-262,-157.81512451171875,-262,-130,-262,-130,-242.305565516154,-130,-222.61113103230795,-130,-212.61113103230795], "text":"Si"}, {"from":-6, "to":-8, "fromPort":"L", "toPort":"T", "visible":true, "points":[-280.18487548828125,-262,-290.18487548828125,-262,-311,-262,-311,-243.305565516154,-311,-224.61113103230795,-311,-214.61113103230795], "text":"No"}, {"from":-5, "to":-9, "fromPort":"B", "toPort":"T", "points":[-35,-223.31998565673825,-35,-213.31998565673825,-35,-212,-35,-212,-35,-154,-130,-154,-130,-134.26667861938478,-130,-124.26667861938478]}, {"from":-7, "to":-9, "fromPort":"B", "toPort":"T", "points":[-130,-182.31998565673828,-130,-172.31998565673828,-130,-153.29333213806154,-130,-153.29333213806154,-130,-134.26667861938478,-130,-124.26667861938478]}, {"from":-8, "to":-9, "fromPort":"B", "toPort":"T", "points":[-311,-184.31998565673828,-311,-174.31998565673828,-311,-154.29333213806154,-130,-154.29333213806154,-130,-134.26667861938478,-130,-124.26667861938478]} ]}})

Como se puede ver en este ejemplo, en los diagramas, las expresiones relacionales se escriben empleando los operadores relacionales estándar de JavaScript, pero, para la igualdad y la diferencia, se emplean los operadores == y !=, NO === y !==, porque esos operadores son exclusivo de JavaScript, no obstante, al momento de programar, se aconseja emplear estos operadores, porque, al ser más estrictos, son más seguros (menos propensos a ocasionar errores).

Para satisfacer la condición implícita del algoritmo (el número "n" debe ser entero), se pregunta si el número no es entero (n%1!==0) y de ser así se lanza (con throw) un error con la cadena: "El número debe ser entero.". Al proceso de verificar que la información (los datos) sean del tipo correcto y que sus valores estén dentro del conjunto de valores permitidos, se denomina validación:

var parImparCero = n => {
if (n%1!==0) throw "El número debe ser entero";
if (n === 0) return "cero";
else
if (n%2 === 0) return "par";
else return "impar";
};

El comando throw, empleado en este capítulo, lanza un error y detiene la ejecución del programa, entonces el control del programa pasa al primer bloque try-catch que encierra a throw. Si dicho bloque no existe (como en este ejemplo) el error es atrapado por el primer bloque try-catch externo. Cuando se trabaja en la calculadora, si no existe ningún bloque try-catch, el error es atrapado por el bloque try-catch de la calculadora, el cual simplemente muestra el mensaje de error.

La estructura try-catch será estudiada en capítulos posteriores, por el momento es suficiente saber que atrapa los errores y que la calculadora tiene un bloque try-catch, que atrapa los errores que no hubieran sido atrapados en el código, mostrando el mensaje del error en la calculadora.

parImparCero(0)
parImparCero(115)
parImparCero(244)
parImparCero(-115)
parImparCero(-110)
parImparCero(34.53)
El número debe ser entero
parImparCero(-7.3265e2)
El número debe ser entero

Alternativamente, para determinar si un número es entero, se puede emplear el método estático isInteger de la clase Number, el cual devuelve true si el número es entero y false en caso contrario.

El diagrama de flujo (la lógica), verificando primero si el número es impar y luego si es cero, es:

gojsGraph({divi, modelo:{ "class": "go.GraphLinksModel", "linkFromPortIdProperty": "fromPort", "linkToPortIdProperty": "toPort", "nodeDataArray": [ {"category":"Conditional", "text":"n%2==1", "key":-4, "loc":"-131 -319"}, {"category":"Start", "text":"parImparCero_", "key":-1, "loc":"-131 -428"}, {"category":"Input", "text":"número entero: n", "key":-3, "loc":"-131 -377"}, {"category":"Output", "text":"\"impar\"", "key":-5, "loc":"-35 -236"}, {"category":"Conditional", "text":"n==0", "key":-6, "loc":"-224 -262"}, {"category":"Output", "text":"\"cero\"", "key":-7, "loc":"-130 -195"}, {"category":"Output", "text":"\"par\"", "key":-8, "loc":"-311 -197"}, {"category":"Start", "text":"fin", "key":-9, "loc":"-130 -109"} ], "linkDataArray": [ {"from":-1, "to":-3, "fromPort":"B", "toPort":"T", "points":[-131,-412.7333213806152,-131,-402.7333213806152,-131,-402,-131,-402,-131,-401.2666786193848,-131,-391.2666786193848]}, {"from":-3, "to":-4, "fromPort":"B", "toPort":"T", "points":[-131,-362.7333213806152,-131,-352.7333213806152,-131,-350.8833393096924,-130.99999999999997,-350.8833393096924,-130.99999999999997,-349.0333572387696,-130.99999999999997,-339.0333572387696]}, {"from":-4, "to":-5, "fromPort":"R", "toPort":"T", "visible":true, "points":[-74.81512451171872,-319.00000000000006,-64.81512451171872,-319.00000000000006,-35.000000000000014,-319.00000000000006,-35.000000000000014,-291.30556551615393,-35.000000000000014,-263.6111310323078,-35.000000000000014,-253.6111310323078], "text":"Si"}, {"from":-4, "to":-6, "fromPort":"L", "toPort":"T", "visible":true, "points":[-187.18487548828122,-319.00000000000006,-197.18487548828122,-319.00000000000006,-223.99999999999994,-319.00000000000006,-223.99999999999994,-305.51667861938483,-223.99999999999994,-292.0333572387696,-223.99999999999994,-282.0333572387696], "text":"No"}, {"from":-6, "to":-7, "fromPort":"R", "toPort":"T", "visible":true, "points":[-187.84066772460932,-262.00000000000006,-177.84066772460932,-262.00000000000006,-130.00000000000003,-262.00000000000006,-130.00000000000003,-242.30556551615402,-130.00000000000003,-222.61113103230798,-130.00000000000003,-212.61113103230798], "text":"Si"}, {"from":-6, "to":-8, "fromPort":"L", "toPort":"T", "visible":true, "points":[-260.15933227539057,-262.00000000000006,-270.15933227539057,-262.00000000000006,-310.9999999999999,-262.00000000000006,-310.9999999999999,-243.30556551615393,-310.9999999999999,-224.61113103230778,-310.9999999999999,-214.61113103230778], "text":"No"}, {"from":-5, "to":-9, "fromPort":"B", "toPort":"T", "points":[-35.000000000000014,-223.31998565673814,-35.000000000000014,-213.31998565673814,-35.000000000000014,-212,-35.000000000000014,-212,-35.000000000000014,-155,-130,-155,-130,-134.26667861938478,-130,-124.26667861938476]}, {"from":-7, "to":-9, "fromPort":"B", "toPort":"T", "points":[-130.00000000000003,-182.3199856567383,-130.00000000000003,-172.3199856567383,-130,-172.3199856567383,-130,-172.3199856567383,-130,-134.26667861938478,-130,-124.26667861938476]}, {"from":-8, "to":-9, "fromPort":"B", "toPort":"T", "points":[-310.9999999999999,-184.3199856567381,-310.9999999999999,-174.3199856567381,-310.9999999999999,-154.29333213806143,-130,-154.29333213806143,-130,-134.26667861938478,-130,-124.26667861938476]} ]}})

Siendo el código respectivo (validando en este caso, que el número sea entero, con el método estático isInteger), es:

function parImparCero(n) {
if (!Number.isInteger(n)) throw "El número debe ser entero";
if (n%2 === 1) return "impar";
else
if (n === 0) return "cero";
else return "par";
};

Con el cual, por supuesto, se obtienen los mismos resultados:

parImparCero(0)
parImparCero(115)
parImparCero(244)
parImparCero(-115)
parImparCero(-110)
parImparCero(34.53)
El número debe ser entero.
parImparCero(-7.3265e2)
El número debe ser entero.

Analizando con más detenimiento la lógica, se puede ver que, en todos los casos, una vez que se cumple una de las condiciones (cualquiera) el resultado es devuelto directamente y la función termina, sin realizar ninguna operación o procesamiento posterior, por lo tanto, en este tipo de problemas, no se requieren los casos contrarios (los else), porque si no se cumple un caso, el flujo del programa pasa de todas formas al caso contrario (sin requerir el else).

Sin embargo, se aclara que esto ocurre únicamente porque no se ejecuta ninguna instrucción después del bloque if-else. Si antes de salir del programa, sería necesario ejecutar alguna instrucción (cualquiera) sería necesario emplear los casos contrarios (los else).

Por lo tanto, para estos casos, el algoritmo puede ser programado de la siguiente forma:

var parImparCero_ = function(n) {
if (!Number.isInteger(n)) throw "El número debe ser entero";
if (n%2 === 1) return "impar";
if (n === 0) return "cero";
return "par";
};

Año bisiesto

En este ejemplo se resuelve el problema de determinar si un año (un número entero positivo) es (true) o no (false) bisiesto.

Un año es bisiesto si es divisible entre 4, excepto cuando termina en dos ceros, en cuyo caso, se verifica si el número, sin los dos ceros, es divisible entre 4, o, lo que es lo mismo, se verifica si el número es divisible entre 400. Así, si el año es 1900, se verifica si 19 es divisible entre 4 (19%4==0) o (lo que es lo mismo) si 1900 es divisible entre 400 (1900%400 = 19%4 == 0).

Como casi siempre ocurre en programación, existe más de una forma de resolver el mismo problema. Así, se puede verificar primero si el año es divisible entre 100 y de ser así se verifica si el año es divisible entre 400, caso contrario, se verifica si es divisible entre 4, como se muestra en el siguiente diagrama de flujo:

gojsGraph({divi, modelo:{ "class": "go.GraphLinksModel", "linkFromPortIdProperty": "fromPort", "linkToPortIdProperty": "toPort", "nodeDataArray": [ {"category":"Conditional", "text":"a%100==0", "key":-4, "loc":"-131 -319"},{"category":"Start", "text":"esBisiesto", "key":-1, "loc":"-131 -428"},{"category":"Input", "text":"año: a", "key":-3, "loc":"-131 -377"},{"category":"Output", "text":"a%400==0", "key":-5, "loc":"-13.000000000000014 -252.99999999999986"},{"category":"Output", "text":"a%4==0", "key":-8, "loc":"-238.9999999999999 -252.99999999999983"},{"category":"Start", "text":"fin", "key":-9, "loc":"-130.99999999999997 -149"} ], "linkDataArray": [ {"from":-1, "to":-3, "fromPort":"B", "toPort":"T", "points":[-131.00000000000003,-412.7333213806152,-131.00000000000003,-402.7333213806152,-131.00000000000003,-402,-130.99999999999994,-402,-130.99999999999994,-401.2666786193848,-130.99999999999994,-391.2666786193848]},{"from":-3, "to":-4, "fromPort":"B", "toPort":"T", "points":[-130.99999999999994,-362.7333213806152,-130.99999999999994,-352.7333213806152,-130.99999999999994,-350.8833393096924,-130.99999999999997,-350.8833393096924,-130.99999999999997,-349.0333572387696,-130.99999999999997,-339.0333572387696]},{"from":-4, "to":-5, "fromPort":"R", "toPort":"T", "visible":true, "points":[-58.52787780761716,-319.00000000000006,-48.52787780761716,-319.00000000000006,-13.000000000000014,-319.00000000000006,-13.000000000000014,-299.80556551615393,-13.000000000000014,-280.6111310323078,-13.000000000000014,-270.6111310323078], "text":"Si"},{"from":-5, "to":-9, "fromPort":"B", "toPort":"T", "points":[-13.000000000000014,-240.31998565673814,-13.000000000000014,-230.31998565673814,-13.000000000000014,-202.29333213806146,-131,-202.29333213806146,-131,-174.26667861938478,-131,-164.26667861938478]},{"from":-8, "to":-9, "fromPort":"B", "toPort":"T", "points":[-238.9999999999999,-240.31998565673808,-238.9999999999999,-230.31998565673808,-238.9999999999999,-202.29333213806143,-131,-202.29333213806143,-131,-174.26667861938478,-131,-164.26667861938478]},{"from":-4, "to":-8, "fromPort":"L", "toPort":"T", "visible":true, "points":[-203.47212219238278,-319.00000000000006,-213.47212219238278,-319.00000000000006,-238.9999999999999,-319.00000000000006,-238.9999999999999,-299.80556551615393,-238.9999999999999,-280.61113103230775,-238.9999999999999,-270.61113103230775], "text":"No"} ]}})

Al igual que en el ejemplo previo, aunque no está de forma explícita en el algoritmo, en el código se debe validar que el año sea un número entero positivo, porque no existen años ni reales ni negativos. El código respectivo, es:

var esBisiesto = function(a) {
if (a%1!==0 || a<0) throw "¡El año debe ser entero positivo!";
if (a%100 === 0) return a%400 === 0;
return a%4 === 0;
};

La función devuelve siempre un valor booleano (un valor lógico): true o false, porque al ser el resultado de una expresión relacional, es un valor booleano.

Llamando a la función con diferentes valores de prueba, se obtiene:

esBisiesto(1980)
esBisiesto(1982)
esBisiesto(1996)
esBisiesto(2000)
esBisiesto(2006)
esBisiesto(2012)
esBisiesto(2020)
esBisiesto(-2020)
¡El año debe ser entero positivo!
esBisiesto(2020.32)
¡El año debe ser entero positivo!

Otra posible solución, consiste en quitar los dos ceros del año cuando es divisible entre 100, luego, simplemente se verifica si el año (que ya no tiene los dos ceros) es divisible entre 4. Tal como se muestra en el siguiente diagrama de flujo:

gojsGraph({divi, modelo: {"class":"go.GraphLinksModel","linkFromPortIdProperty":"fromPort","linkToPortIdProperty":"toPort","nodeDataArray":[{"category":"Conditional","text":"a%100==0","key":-4,"loc":"-349 -354.00000000000006"},{"category":"Start","text":"esBisiesto","key":-1,"loc":"-349 -463"},{"category":"Input","text":"año: a","key":-3,"loc":"-348.99999999999994 -412"},{"category":"Output","text":"a%4==0","key":-8,"loc":"-349 -242.99999999999986"},{"category":"Start","text":"fin","key":-9,"loc":"-348.9999999999998 -189"},{"category":"Process","text":"a = a/100","key":-2,"loc":"-227.00000000000006 -296"}],"linkDataArray":[{"from":-1,"to":-3,"fromPort":"B","toPort":"T","points":[-349.00000000000006,-447.7333213806152,-349.00000000000006,-437.7333213806152,-349.00000000000006,-437,-348.99999999999994,-437,-348.99999999999994,-436.2666786193848,-348.99999999999994,-426.2666786193848]},{"from":-3,"to":-4,"fromPort":"B","toPort":"T","points":[-348.99999999999994,-397.7333213806152,-348.99999999999994,-387.7333213806152,-348.99999999999994,-385.8833393096924,-349,-385.8833393096924,-349,-384.0333572387696,-349,-374.0333572387696]},{"from":-8,"to":-9,"fromPort":"B","toPort":"T","points":[-348.9999999999999,-230.31998565673808,-348.9999999999999,-220.31998565673808,-348.9999999999999,-217.29333213806143,-349,-217.29333213806143,-349,-214.26667861938478,-349,-204.26667861938478]},{"from":-4,"to":-2,"fromPort":"R","toPort":"T","visible":true,"points":[-276.5278778076172,-354.00000000000006,-266.5278778076172,-354.00000000000006,-227,-354.00000000000006,-227,-338.6333393096924,-227,-323.2666786193848,-227,-313.2666786193848],"text":"Si"},{"from":-2,"to":-8,"fromPort":"B","toPort":"R","points":[-227,-278.7333213806152,-227,-268.7333213806152,-227,-242.9999999999998,-262.00836563110346,-242.9999999999998,-297.0167312622069,-242.9999999999998,-307.0167312622069,-242.9999999999998]},{"from":-4,"to":-8,"fromPort":"B","toPort":"T","visible":true,"points":[-349,-333.96664276123056,-349,-323.96664276123056,-349,-297.28888689676916,-348.9999999999999,-297.28888689676916,-348.9999999999999,-270.61113103230775,-348.9999999999999,-260.61113103230775],"text":"No"}]}})

Siendo el código respectivo (en forma de función flecha):

var esBisiesto_ = a => {
if (a%1 !==0 || a<0) throw "¡El año debe ser entero positivo!";
if (a%100 === 0) a = a/100;
return a%4 === 0;
};

Que, por supuesto, devuelve los mismos resultados que con la lógica anterior:

esBisiesto_(1980)
esBisiesto_(1982)
esBisiesto_(1996)
esBisiesto_(2000)
esBisiesto_(2006)
esBisiesto_(2012)
esBisiesto_(2020)
esBisiesto_(-2020)
¡El año debe ser entero positivo!
esBisiesto_(2020.32)
¡El año debe ser entero positivo!

Mayor de 3 números

En este ejemplo se elabora una función que recibe tres números y devuelve el mayor ellos.

Como de costumbre, se pueden plantear varias alternativas de solución. Probablemente la más sencilla consiste en determinar si el primer número es el mayor, de no ser así, se comprueba si el segundo es el mayor y de no ser así el mayor (por defecto) es el tercero.

Por supuesto, el orden puede ser cambiado sin que en realidad cambie la lógica. El algoritmo, en forma de diagrama de flujo, es:

gojsGraph({divi, modelo: {"class":"go.GraphLinksModel","linkFromPortIdProperty":"fromPort","linkToPortIdProperty":"toPort","nodeDataArray":[{"category":"Conditional","text":"a>=b y a>=c","key":-4,"loc":"-144 -345.00000000000006"},{"category":"Start","text":"mayor","key":-1,"loc":"-144 -454"},{"category":"Input","text":"números: a, b, c","key":-3,"loc":"-143.99999999999994 -403"},{"category":"Start","text":"fin","key":-9,"loc":"-145 -147"},{"category":"Output","text":"a","key":-7,"loc":"-15.999999999999886 -221.99999999999983"},{"category":"Output","text":"c","key":-10,"loc":"-388.9999999999999 -221.99999999999983"},{"category":"Output","text":"b","key":-11,"loc":"-144.9999999999999 -221.99999999999983"},{"category":"Conditional","text":"b>=a y b>=c","key":-8,"loc":"-271 -282.00000000000006"}],"linkDataArray":[{"from":-1,"to":-3,"fromPort":"B","toPort":"T","points":[-144,-438.7333213806152,-144,-428.7333213806152,-144,-428,-143.99999999999994,-428,-143.99999999999994,-427.2666786193848,-143.99999999999994,-417.2666786193848]},{"from":-3,"to":-4,"fromPort":"B","toPort":"T","points":[-143.99999999999994,-388.7333213806152,-143.99999999999994,-378.7333213806152,-143.99999999999994,-376.8833393096924,-144,-376.8833393096924,-144,-375.0333572387696,-144,-365.0333572387696]},{"from":-4,"to":-7,"fromPort":"R","toPort":"T","visible":true,"points":[-64.41780090332031,-345.00000000000006,-54.41780090332031,-345.00000000000006,-15.999999999999886,-345.00000000000006,-15.999999999999886,-297.30556551615393,-15.999999999999886,-249.61113103230775,-15.999999999999886,-239.61113103230775],"text":"Si"},{"from":-4,"to":-8,"fromPort":"L","toPort":"T","visible":true,"points":[-223.5821990966797,-345.00000000000006,-233.5821990966797,-345.00000000000006,-271,-345.00000000000006,-271,-328.51667861938483,-271,-312.0333572387696,-271,-302.0333572387696],"text":"No"},{"from":-8,"to":-11,"fromPort":"R","toPort":"T","visible":true,"points":[-190.65548706054688,-282.00000000000006,-180.65548706054688,-282.00000000000006,-144.9999999999999,-282.00000000000006,-144.9999999999999,-265.80556551615393,-144.9999999999999,-249.61113103230775,-144.9999999999999,-239.61113103230775],"text":"Si"},{"from":-8,"to":-10,"fromPort":"L","toPort":"T","visible":true,"points":[-351.3445129394531,-282.00000000000006,-361.3445129394531,-282.00000000000006,-388.9999999999999,-282.00000000000006,-388.9999999999999,-265.80556551615393,-388.9999999999999,-249.61113103230775,-388.9999999999999,-239.61113103230775],"text":"No"},{"from":-11,"to":-9,"fromPort":"B","toPort":"T","points":[-144.9999999999999,-209.31998565673808,-144.9999999999999,-199.31998565673808,-144.9999999999999,-185.79333213806143,-145,-185.79333213806143,-145,-172.26667861938478,-145,-162.26667861938478]},{"from":-7,"to":-9,"fromPort":"B","toPort":"R","points":[-15.999999999999886,-209.31998565673808,-15.999999999999886,-199.31998565673808,-15.999999999999886,-147,-63.72024857964403,-147,-111.44047715928818,-147,-121.44047715928818,-147]},{"from":-10,"to":-9,"fromPort":"B","toPort":"L","points":[-388.9999999999999,-209.31998565673808,-388.9999999999999,-199.31998565673808,-388.9999999999999,-147,-283.77976142035584,-147,-178.5595228407118,-147,-168.5595228407118,-147]}]}})

El código respectivo, implementado en forma de una función estándar, es:

function mayor(a, b, c) {
if (!Number.isFinite(a) || !Number.isFinite(b) || !Number.isFinite(c))
throw "Los datos deben ser de tipo numérico."
if (a>=b && a>=c) return a;
if (b>=a && b>=c) return b;
return c;
};

En este caso, aunque los números pueden ser enteros o reales, se valida que efectivamente sean números (no booleanos, cadenas o arreglos). Con ese fin, se recurre a la función estático isFinite de la clase Number, el cual devuelve true si es un número válido y false en caso contrario.

Llamando a la función con algunos valores (arrays) de prueba, se obtiene:

mayor(1, 2, 3)
mayor(1, 3, 2)
mayor(3, 2, 1)
mayor(5, 4, 5)
mayor(4, 7, 4)
mayor(8, 8, 8)

Ecuación cuadrática

En este ejemplo se elabora una función que devuelve las dos soluciones de la ecuación cuadrática:

\[ a x^2 + b x + c= 0 \]

Programando la solución general:

\[ x_{1,2} = \dfrac{-b \pm \sqrt{b^2-4 a c}}{2 a} \]

Las soluciones de la ecuación dependen del valor de la expresión que se encuentra dentro de la raíz cuadrada, denominado discriminante ("d = b2−4ac"). Si el discriminante es positivo, las dos soluciones son reales y distintas (porque la raíz es mayor a cero), si es cero, las soluciones son iguales (porque la raíz es cero) y si es negativo, las soluciones son complejas: un par conjugado (porque la raíz es negativa: imaginaria).

Entonces, para resolver el problema se calcula el valor del discriminante, luego se puede determinar si el discriminantes es positivo, negativo o cero, en cualquier orden, así en la siguiente solución, se determina primero si el discriminante es negativo y luego si es cero, siendo el caso por defecto positivo:

gojsGraph({divi, modelo:{ "class": "go.GraphLinksModel", "linkFromPortIdProperty": "fromPort", "linkToPortIdProperty": "toPort", "nodeDataArray": [ {"category":"Start", "text":"cuadratica", "key":-1, "loc":"172 -460"},{"category":"Input", "text":"coeficientes: a, b, c", "key":-3, "loc":"172.00000000000009 -410.9999999999998"},{"category":"Conditional", "text":"d<0", "key":-5, "loc":"171.99999999999994 -297.99999999999994"},{"category":"Start", "text":"fin", "key":-9, "loc":"171 15.000000000000043"},{"category":"Output", "text":"r1, r2", "key":-10, "loc":"171 -36"},{"category":"Process", "text":"d = b**2-4*a*c", "key":-11, "loc":"172 -357"},{"category":"Process", "text":"r = -b/(2*a)", "key":-12, "loc":"303 -245"},{"category":"Process", "text":"im = sqrt(-d)/(2*a)", "key":-13, "loc":"303 -190"},{"category":"Process", "text":"r1 = Complex(r,im)", "key":-14, "loc":"303 -136"},{"category":"Process", "text":"r2= Complex(r,-im)", "key":-15, "loc":"303 -79"},{"category":"Conditional", "text":"d==0", "key":-16, "loc":"61.99999999999994 -237.99999999999994"},{"category":"Process", "text":"r2 = r1", "key":-17, "loc":"171 -110"},{"category":"Process", "text":"r1 = -b/(2*a)", "key":-18, "loc":"171 -165"},{"category":"Process", "text":"r1 = (-b+sqrt(d))/(2*a)", "key":-19, "loc":"-25 -165"},{"category":"Process", "text":"r2 = (-b-sqrt(d))/(2*a)", "key":-20, "loc":"-25 -110"} ], "linkDataArray": [ {"from":-1, "to":-3, "fromPort":"B", "toPort":"T", "points":[172.00000000000006,-444.7333213806151,172.00000000000006,-434.7333213806151,172.00000000000006,-434.7333213806151,172.00000000000006,-435.26667861938455,172.00000000000009,-435.26667861938455,172.00000000000009,-425.26667861938455]},{"from":-10, "to":-9, "fromPort":"B", "toPort":"T", "points":[171,-23.31998565673828,171,-13.319985656738279,171,-11.793332138061507,171.00000000000006,-11.793332138061507,171.00000000000006,-10.266678619384734,171.00000000000006,-0.26667861938473436]},{"from":-12, "to":-13, "fromPort":"B", "toPort":"T", "points":[303,-227.73332138061525,303,-217.73332138061525,303,-217.5,303,-217.5,303,-217.26667861938478,303,-207.26667861938478]},{"from":-13, "to":-14, "fromPort":"B", "toPort":"T", "points":[303,-172.73332138061525,303,-162.73332138061525,303,-162.73332138061525,303,-163.26667861938478,303,-163.26667861938478,303,-153.26667861938478]},{"from":-14, "to":-15, "fromPort":"B", "toPort":"T", "points":[303,-118.73332138061525,303,-108.73332138061525,303,-107.5,303,-107.5,303,-106.26667861938476,303,-96.26667861938476]},{"from":-5, "to":-12, "fromPort":"R", "toPort":"T", "visible":true, "points":[199.70051574707026,-297.99999999999994,209.70051574707026,-297.99999999999994,303,-297.99999999999994,303,-285.13333930969236,303,-272.2666786193848,303,-262.2666786193848], "text":"Si"},{"from":-18, "to":-17, "fromPort":"B", "toPort":"T", "points":[171,-147.73332138061525,171,-137.73332138061525,171,-137.5,171,-137.5,171,-137.26667861938478,171,-127.26667861938478]},{"from":-19, "to":-20, "fromPort":"B", "toPort":"T", "points":[-25,-147.73332138061525,-25,-137.73332138061525,-25,-137.5,-25,-137.5,-25,-137.26667861938478,-25,-127.26667861938478]},{"from":-16, "to":-18, "fromPort":"R", "toPort":"T", "visible":true, "points":[98.2033081054687,-237.99999999999997,108.2033081054687,-237.99999999999997,171,-237.99999999999997,171,-215.13333930969236,171,-192.26667861938478,171,-182.26667861938478], "text":"Si"},{"from":-16, "to":-19, "fromPort":"L", "toPort":"T", "visible":true, "points":[25.796691894531193,-237.99999999999997,15.796691894531193,-237.99999999999997,-25,-237.99999999999997,-25,-215.13333930969236,-25,-192.26667861938478,-25,-182.26667861938478], "text":"No"},{"from":-5, "to":-16, "fromPort":"L", "toPort":"T", "visible":true, "points":[144.29948425292963,-297.99999999999994,134.29948425292963,-297.99999999999994,61.99999999999994,-297.99999999999994,61.99999999999994,-283.0166786193847,61.99999999999994,-268.0333572387695,61.99999999999994,-258.0333572387695], "text":"No"},{"from":-3, "to":-11, "fromPort":"B", "toPort":"T", "points":[172.00000000000009,-396.733321380615,172.00000000000009,-386.733321380615,172.00000000000009,-385.4999999999999,172,-385.4999999999999,172,-384.2666786193848,172,-374.2666786193848]},{"from":-11, "to":-5, "fromPort":"B", "toPort":"T", "points":[172,-339.7333213806152,172,-329.7333213806152,172,-328.88333930969236,171.99999999999994,-328.88333930969236,171.99999999999994,-328.0333572387695,171.99999999999994,-318.0333572387695]},{"from":-17, "to":-10, "fromPort":"B", "toPort":"T", "points":[171,-92.73332138061525,171,-82.73332138061525,171,-73.1722262064616,171,-73.1722262064616,171,-63.611131032307945,171,-53.611131032307945]},{"from":-15, "to":-10, "fromPort":"B", "toPort":"R", "points":[303,-61.73332138061522,303,-51.73332138061522,303,-36,258.43970489501953,-36,213.87940979003906,-36,203.87940979003906,-36]},{"from":-20, "to":-10, "fromPort":"B", "toPort":"L", "points":[-25,-92.73332138061525,-25,-82.73332138061525,-25,-36,51.56029510498047,-36,128.12059020996094,-36,138.12059020996094,-36]} ]}})

Al igual que en el ejemplo anterior, se valida que los tres datos sean efectivamente números (con isFinite):

var cuadratica = (a, b, c) => {
const d = b**2-4*a*c;
let r, i, r1, r2;
if (d<0) {
r = -b/(2*a);
i = Math.sqrt(-d)/(2*a);
r1 = Complex(r, i);
r2 = Complex(r, -i);
} else
if (d===0) {
r1 = -b/(2*a);
r2 = r1;
} else {
r1 = (-b+Math.sqrt(d))/(2*a);
r2 = (-b-Math.sqrt(d))/(2*a);
}
return [r1, r2];
};

Como se puede ver, para devolver los resultados complejos, se ha empleado la clase Complex, la cual crea un número complejo a partir de las partes real e imaginaria del número.

Llamando a la función con los coeficientes de algunas ecuaciones cuadráticas, se obtiene:

cuadratica(1, -10, 21)
cuadratica(1, -14, 49)
cuadratica(1, 2, 3)