30. Práce s úhly v CombiScriptu: od Pi po Atan2

Ilustrační obrázek k článku: Práce s úhly v CombiScriptu: od Pi po Atan2
Ukážeme, jak CombiScript sjednocuje práci s úhly mezi PHP a JS. Implementujeme goniometrické (sin, cos), cyklometrické (arcsin, arccos) i převodní funkce – a vysvětlíme, proč atan2 má parametry ($y, $x).

Prvním krokem při práci s úhly je mít po ruce přesnou hodnotu π (Pi). V CombiScriptu ji získáte jednoduše – stačí zavolat funkci nm_Pi().

  1. function nm_Pi()
  2. {
  3. 0&&0<!--$_;/*
  4. return Math.PI;
  5. /*/
  6. return Pi();
  7. //*/
  8. }

Než se pustíme do detailů, shrňme si, jak jsem celou práci s úhly v CombiScriptu rozdělil. Pro přehlednost a logiku jsem zvolil tři základní skupiny funkcí:

A jako bonus – funkci atan2, která řeší, co obyčejný tangens nezvládne: převod kartézských souřadnic na úhel včetně správného kvadrantu.

Převodní funkce: radiány ↔ stupně

Nemůžeme zapomenout na převody mezi radiány a stupni. Zatímco PHP má vestavěné funkce rad2deg() a deg2rad(), JavaScriptu si je musíme napsat sami – což v CombiScriptu elegantně vyřešíme.

  1. function nm_Rad2Deg($rad)
  2. {
  3. if (!nm_IsNumber($rad)) throw new cs_Error('nm_Rad2Deg-$rad-NotNumber', [cs_Type($rad)]);
  4. 0&&0<!--$_;/*
  5. return $rad * (180/Math.PI);
  6. /*/
  7. return rad2deg($rad);
  8. //*/
  9. }

A samozřejmě i opačný převod – stupně na radiány.

  1. function nm_Deg2Rad($deg)
  2. {
  3. if (!nm_IsNumber($deg)) throw new cs_Error('nm_Deg2Rad-$deg-NotNumber', [cs_Type($deg)]);
  4. 0&&0<!--$_;/*
  5. return $deg/180*Math.PI;
  6. /*/
  7. return deg2rad($deg);
  8. //*/
  9. }

Goniometrické funkce: úhel → poměr

Teď přichází na řadu „klasika“ – goniometrické funkce. Začneme s sinem. Jako parametr jsem zvolil radiány – je to mezinárodní standard, který dodržuje většina jazyků. (I když já osobně mám pořád slabost pro stupně :-)

  1. function nm_Sin($rad)
  2. {
  3. if (!nm_IsNumber($rad)) throw new cs_Error('nm_Sin-$rad-NotNumber', [cs_Type($rad)]);
  4. 0&&0<!--$_;/*
  5. return Math.sin($rad);
  6. /*/
  7. return sin($rad);
  8. //*/
  9. }

Pokračujeme kosinem.

  1. function nm_Cos($rad)
  2. {
  3. if (!nm_IsNumber($rad)) throw new cs_Error('nm_Cos-$rad-NotNumber', [cs_Type($rad)]);
  4. 0&&0<!--$_;/*
  5. return Math.cos($rad);
  6. /*/
  7. return cos($rad);
  8. //*/
  9. }

A nesmí chybět tangens.

  1. function nm_Tan($rad)
  2. {
  3. if (!nm_IsNumber($rad)) throw new cs_Error('nm_Tan-$rad-NotNumber', [cs_Type($rad)]);
  4. 0&&0<!--$_;/*
  5. return Math.tan($rad);
  6. /*/
  7. return tan($rad);
  8. //*/
  9. }

Cyklometrické funkce: poměr → úhel

Samozřejmě budeme potřebovat i funkce inverzní – tedy cyklometrické. Ty z poměru (čísla) počítají zpět úhel. U těchto funkcí navíc kontrolujeme, zda vstupní hodnota leží v povoleném rozsahu.

První z nich je arkus sinus.

Cyklometrické funkce nejsou jednoznačné – jeden vstup může mít více správných výstupů (např. arcsin(-1) = -π/2, ale také 3π/2). Aby naše knihovna fungovala konzistentně, musíme ověřit v testech, že oba jazyky vrací stejnou hlavní hodnotu (obvykle v rozsahu <-π/2, π/2>). Pokud ne, upravíme výsledek přičtením nebo odečtením 2π.

  1. function nm_ASin($num)
  2. {
  3. if (!nm_IsNumber($num)) throw new cs_Error('nm_ASin-$num-NotNumber', [cs_Type($num)]);
  4. if ($num>1 || $num<-1) throw new cs_Error('nm_ASin-$num-OutOfRange');
  5. 0&&0<!--$_;/*
  6. return Math.asin($num);
  7. /*/
  8. return asin($num);
  9. //*/
  10. }

Dále následuje arkus kosinus.

  1. function nm_ACos($num)
  2. {
  3. if (!nm_IsNumber($num)) throw new cs_Error('nm_ACos-$num-NotNumber', [cs_Type($num)]);
  4. if ($num>1 || $num<-1) throw new cs_Error('nm_ACos-$num-OutOfRange');
  5. 0&&0<!--$_;/*
  6. return Math.acos($num);
  7. /*/
  8. return acos($num);
  9. //*/
  10. }

A jako poslední z této trojice arkus tangens.

  1. function nm_ATan($num)
  2. {
  3. if (!nm_IsNumber($num)) throw new cs_Error('nm_ATan-$num-NotNumber', [cs_Type($num)]);
  4. 0&&0<!--$_;/*
  5. return Math.atan($num);
  6. /*/
  7. return atan($num);
  8. //*/
  9. }

atan2: když nestačí jen tangens

Pokud potřebujete převést kartézské souřadnice na polární úhel, nestačí vám obyčejný nm_ATan. Problém? Tangens neumí rozhodnout, ve kterém kvadrantu se bod nachází – stejný poměr y/x může odpovídat dvěma různým úhlům.

Řešení? Funkce ATan2. Ta jako parametry bere přímo obě souřadnice – a správně určí kvadrant.

  1. function nm_ATan2($y, $x)
  2. {
  3. if (!nm_IsNumber($y)) throw new cs_Error('nm_ATan2-$y-NotNumber', [cs_Type($y)]);
  4. if (!nm_IsNumber($x)) throw new cs_Error('nm_ATan2-$x-NotNumber', [cs_Type($x)]);
  5. 0&&0<!--$_;/*
  6. return Math.atan2($y, $x);
  7. /*/
  8. return atan2($y, $x);
  9. //*/
  10. }

Parametry uvádíme v pořadí ($y, $x) – přesně tak, jak to zavedl Fortran v 60. letech. (Ano, v prvním manuálu z roku 1956 funkce atan2 ještě neexistovala – takže pokud si ho pamatujete, gratuluji k věku :-)

Toto pořadí není náhoda: vychází z logiky atan(y/x) – tedy protilehlá ku přilehlé. Většina programovacích jazyků se této konvence drží – a my také.

Experimenty s opačným pořadím (např. v Lotus 1-2-3 a některých tabulkových kalkulátorech) považuji za velmi matoucí. Pokud někdo potřebuje jiné pořadí, měl by zvolit jiný název funkce – třeba atan3(x, y). Prohazování parametrů při stejném názvu vytváří v programování jen zbytečný chaos.

V tomto článku jsme se opakovaně dotkli klíčového slova: testy. Bez nich bychom neměli jistotu, že naše funkce vrací konzistentní výsledky napříč jazyky – třeba že nm_ASin(-1) nevrací v PHP -π/2 a v JS 3π/2, i když obě hodnoty jsou matematicky správné. Tuto jistotu ale chceme mít neustále. A proto se v příštím díle podrobně podíváme na automatizované testování našich knihoven.

Předchozí