Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

alternatives to Math.radians and Math.degrees #19

Open
Andrew-Cottrell opened this issue May 30, 2020 · 5 comments
Open

alternatives to Math.radians and Math.degrees #19

Andrew-Cottrell opened this issue May 30, 2020 · 5 comments

Comments

@Andrew-Cottrell
Copy link

Andrew-Cottrell commented May 30, 2020

I have found the following two functions to be applicable in more use cases (#5 (comment)) than the two proposed functions, Math.radians and Math.degrees

  • Math.toRadians( angle, perigon = 360 )
// `displacement` & `amplitude` may be in units of pixel for use with requestAnimationFrame
var displacement = amplitude * Math.cos( Math.toRadians( deltaTime, period ) );

// `displacement` & `amplitude` and/or `distance` & `wavelength` may be in units of pixel
var displacement = amplitude * Math.sin( Math.toRadians( distance, wavelength ) );

var radians = Math.toRadians( turns, 1 );
var radians = Math.toRadians( decimalDegrees ); // default perigon is 360
var radians = Math.toRadians( arcminutes + 60 * degrees, 60 * 360 );
var radians = Math.toRadians( arcseconds + 60 * ( arcminutes + 60 * degrees ), 60 * 60 * 360 );
var radians = Math.toRadians( gradians, 400 );
var radians = Math.toRadians( milliturns, 1000 );
var radians = Math.toRadians( binaryAngle, 256 );
  • Math.fromRadians( radians, perigon = 360 )
// e.g. CSS, electromagnetic coils and rotating objects
var turns = Math.fromRadians( radians, 1 );

// e.g. CSS, astronomical and geographic coordinates (latitude and longitude)
var decimalDegrees = Math.fromRadians( radians ); // default perigon is 360

// e.g. CSS, triangulation, surveying, mining, and geology
var gradians = Math.fromRadians( radians, 400 );

// e.g. measurement devices for artillery and satellite watching
var milliturns = Math.fromRadians( radians, 1000 );

// e.g. robotics, navigation, computer games, and digital sensors
var binaryAngle = Math.fromRadians( radians, 256 );

As latitude and longitude are often expressed as decimal degrees in the intervals [−90°, +90°] and [−180°, +180°] respectively, in my implementation I choose to ensure all return values are in an interval [-perigon/2, +perigon/2] (similar to Math.asin and Math.atan). This reduces surprises and may help maintain precision across multiple floating-point operations. This convention equates an angle in radians with the directed minor arc from one point to another on the unit circle, which is useful in some distance calculations.

With a suitable modulo operation (#21), it is simple for calling code to convert from [-perigon/2, +perigon/2] to [0, perigon] if needed

var radians        = Math.mod( Math.toRadians( angle, perigon ), 2 * Math.PI );
var angle          = Math.mod( Math.fromRadians( radians, perigon ), perigon );
var decimalDegrees = Math.mod( Math.fromRadians( radians ), 360 );

I have found that some JavaScript developers do not know the built-in trigonometry functions operate on angles expressed in radians. They assume, perhaps due to previous experience with calculators defaulting to DEG mode, that the following are valid

var opposite = hypotenuse * Math.sin( 45 /* degrees */ );
var degrees = Math.asin( opposite / hypotenuse );

Perhaps the presence of Math.toRadians & Math.fromRadians, and the absence of any function (in the Math namespace) with "degrees" in its name would help them to realise the built-in trigonometry functions expect angles to be expressed in radians.

@Andrew-Cottrell Andrew-Cottrell changed the title alternatives to Math.toDegrees and Math.toRadians alternatives to Math.degrees and Math.radians May 30, 2020
@Andrew-Cottrell Andrew-Cottrell changed the title alternatives to Math.degrees and Math.radians alternatives to Math.radians and Math.degrees May 30, 2020
@Andrew-Cottrell
Copy link
Author

Andrew-Cottrell commented Sep 11, 2020

An example implementation of the two suggested functions

    /**
     * @private
     * @const {number}
     */
    var TAU = 2 * Math.PI;

    /**
     * @private
     * @param {number} turns - An angle in turns.
     * @return {number} The same angle in the left-closed interval from -0.5 to +0.5.
     */
    function turnPrincipalBranch( turns ) {
        return turns - Math.floor( turns + 0.5 );
    }

    /**
     * @param {number} angle - The angle expressed in some unit (default is decimal degrees).
     * @param {number=} perigon - The number of units in a full rotation (default perigon is 360).
     * @return {number} The same angle expressed in radians in the left-closed interval from -π to +π.
     */
    Math.toRadians = function ( angle, perigon ) {
        return turnPrincipalBranch( angle / ( perigon || 360 ) ) * TAU;
    };

    /**
     * @param {number} radians - The angle expressed in radians.
     * @param {number=} perigon - The number of units in a full rotation (default perigon is 360).
     * @return {number} The same angle expressed in some unit (default is decimal degrees).
     */
    Math.fromRadians = function ( radians, perigon ) {
        return turnPrincipalBranch( radians / TAU ) * ( perigon || 360 );
    };

@Rudxain
Copy link

Rudxain commented Apr 29, 2022

Before I realized this proposal existed (and this specific Issue) I was using these polyfills:

/**
converts degrees to radians by default
@param {number} x
@param {number} [y=360] the input scale
@return {number}
*/
Math.angleToRad = function(x, y = 360) {return TAU / +y * +x}
//scale = 360: degrees
//scale = 1: Tau radians

/**
converts radians to degrees by default
@param {number} x
@param {number} [y=360] the output scale
@return {number}
*/
Math.radToAngle = function(x, y = 360) {return +x / (TAU / +y)}
//unary plus is used to replicate the exact error type as the built-in Math methods

But I think the naming toRadians and fromRadians seems better. I just wanted to show the code to give more ideas and inspiration for other names, just in case they're changed

@Rudxain
Copy link

Rudxain commented Jun 14, 2022

Wait, is turnPrincipalBranch calculating the normalized version of the angle? If so, what are the reasons why it might be desirable? I think the function shouldn't normalize angles unless explicitly specified by the user/dev. We could add a 3rd bool arg, or just add the normalizer as a standalone Math method

@Andrew-Cottrell
Copy link
Author

Andrew-Cottrell commented Jun 14, 2022

Wait, is turnPrincipalBranch calculating the normalized version of the angle? If so, what are the reasons why it might be desirable?

As mentioned in my opening comment above

As latitude and longitude are often expressed as decimal degrees in the intervals [−90°, +90°] and [−180°, +180°] respectively, in my implementation I choose to ensure all return values are in an interval [-perigon/2, +perigon/2] (similar to Math.asin and Math.atan). This reduces surprises and may help maintain precision across multiple floating-point operations. This convention equates an angle in radians with the directed minor arc from one point to another on the unit circle, which is useful in some distance calculations.

With a suitable modulo operation (#21), it is simple for calling code to convert from [-perigon/2, +perigon/2] to [0, perigon] if needed.

However, this is simply a choice I made in my implementation; a different choice could be made.


Aside: my use of the term "principal branch" may not be strictly accurate. If we wanted to consider θ and θ + τ as distinct angles then we're kinda getting a Riemann surface where concepts like branch cuts and principal branches apply (like with complex logarithms). This is quite advanced stuff and I guess most people would consider θ and θ + τ to be the same angle (like 6/4 and 3/2 are the same fraction). I can't think of any use cases that would depend on un-normalized angles.

@Rudxain
Copy link

Rudxain commented Jun 14, 2022

Seems good to me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants