rwaldron / proposal-math-extensions

ES Math Extensions
61 stars 7 forks source link

alternatives to Math.radians and Math.degrees #19

Open Andrew-Cottrell opened 4 years ago

Andrew-Cottrell commented 4 years ago

I have found the following two functions to be applicable in more use cases (https://github.com/rwaldron/proposal-math-extensions/issues/5#issuecomment-491365090) than the two proposed functions, Math.radians and Math.degrees

// 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 )`
```javascript
// 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 commented 4 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 (https://github.com/rwaldron/proposal-math-extensions/issues/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 commented 2 years ago

Seems good to me