0

I'm using Node.js with the mathjs package. Mathjs uses radians in its trig functions. I want to include a degree mode for a calculator I'm making. How can I add "deg" after every number within any sin/cos/tan function in a string? This way, mathjs will calculate it using degrees instead so it can work.

Example: 5*(2+3)/sin(4+(3-2)) should become 5*(2+3)/sin(4deg+(3deg-2deg)) However: 5*(2+3)/(4+(3-2)) should not change.

I've looked everywhere: Regular expressions, reddit, google, I've found nothing. The closest I've come is a regular expression that breaks when there are parentheses inside the sin function (/sin\(.*?\)/).

Edit: Yes I have looked at the angle configuration example. No, that doesn't solve my problem: It assumes the user is implementing a single value to calculate the sine of, I am trying to calculate full expressions with a degree mode (mathjs.evaluate())

Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
OIRNOIR
  • 33
  • 6
  • 1
    Matching balanced parentheses is difficult with regular expressions. You should use a recursive-descent parser. – Barmar Jan 13 '22 at 18:03
  • Yes I have read that. No, I can't replace the functions, I need this to be toggle-able. – OIRNOIR Jan 13 '22 at 18:07
  • I suppose you don't want "deg" to be added in `sin(atan(5))`? – trincot Jan 13 '22 at 18:09
  • But the example already has it toggleable... it's replacing the functions with versions that respect the toggle... Playing with `deg` won't work anyway because you may have arcus functions as well, e.g. `sin(15 + acos(0.5))` should be interpreted as `sin(15deg + acos(0.5))` and not `sin(15deg + acos(0.5deg))`, and instead you would want `acos` to _return_ degrees here. Replacing the functions should allow you to do all that correctly. – CherryDT Jan 13 '22 at 18:09
  • "It assumes the user is implementing a single value to calculate the sine of, I am trying to calculate full expressions with a degree mode (mathjs.evaluate())" - that's not true. – CherryDT Jan 13 '22 at 18:11
  • Thank you for correcting me, CherryDT. – OIRNOIR Jan 13 '22 at 18:14
  • Correct, trincot – OIRNOIR Jan 13 '22 at 18:15

1 Answers1

1

You could instead simply replace the trig function implementations with versions that use degrees...

Look at the angle configuration example in the MathJS docs. You can use the math.import function to provide your own function implementations for sin, cos, etc., and you can refer to the original ones by first saving a reference to them.

Here is the full example from the linked docs as runnable snippet (copyright © 2013-2021 jos de jong):

  let replacements = {}

  // our extended configuration options
  const config = {
    angles: 'deg' // 'rad', 'deg', 'grad'
  }

  // create trigonometric functions replacing the input depending on angle config
  const fns1 = ['sin', 'cos', 'tan', 'sec', 'cot', 'csc']
  fns1.forEach(function(name) {
    const fn = math[name] // the original function

    const fnNumber = function (x) {
      // convert from configured type of angles to radians
      switch (config.angles) {
        case 'deg':
          return fn(x / 360 * 2 * Math.PI)
        case 'grad':
          return fn(x / 400 * 2 * Math.PI)
        default:
          return fn(x)
      }
    }

    // create a typed-function which check the input types
    replacements[name] = math.typed(name, {
      'number': fnNumber,
      'Array | Matrix': function (x) {
        return math.map(x, fnNumber)
      }
    })
  })

  // create trigonometric functions replacing the output depending on angle config
  const fns2 = ['asin', 'acos', 'atan', 'atan2', 'acot', 'acsc', 'asec']
  fns2.forEach(function(name) {
    const fn = math[name] // the original function

    const fnNumber = function (x) {
      const result = fn(x)

      if (typeof result === 'number') {
        // convert to radians to configured type of angles
        switch(config.angles) {
          case 'deg':  return result / 2 / Math.PI * 360
          case 'grad': return result / 2 / Math.PI * 400
          default: return result
        }
      }

      return result
    }

    // create a typed-function which check the input types
    replacements[name] = math.typed(name, {
      'number': fnNumber,
      'Array | Matrix': function (x) {
        return math.map(x, fnNumber)
      }
    })
  })

  // import all replacements into math.js, override existing trigonometric functions
  math.import(replacements, {override: true})

  // pointers to the input elements
  const expression = document.getElementById('expression')
  const evaluate   = document.getElementById('evaluate')
  const result     = document.getElementById('result')
  const angles     = document.getElementById('angles')

  // attach event handlers for select box and button
  angles.onchange = function () {
    config.angles = this.value
    config.angles = this.value
  }
  evaluate.onclick = function () {
    result.innerHTML = math.evaluate(expression.value)
  }
<script src="https://unpkg.com/mathjs@10.0.2/lib/browser/math.js"></script>

<table>
  <tr>
    <th>Angles</th>
    <td>
      <select id="angles">
        <option value="deg">deg</option>
        <option value="grad">grad</option>
        <option value="rad">rad</option>
      </select>
    </td>
  </tr>
  <tr>
    <th>Expression</th>
    <td>
      <input id="expression" type="text" value="sin(45)" />
      <input id="evaluate" type="button" value="Evaluate">
    </td>
  </tr>
  <tr>
    <th>Result</th>
    <td id="result"></td>
  </tr>
</table>

EDIT: To respond to the edit in the question:

Edit: Yes I have looked at the angle configuration example. No, that doesn't solve my problem: It assumes the user is implementing a single value to calculate the sine of, I am trying to calculate full expressions with a degree mode (mathjs.evaluate())

That's not true. It doesn't assume that. The example uses math.evaluate just as you said. It should work exactly as you need it to. The way it works is just that the new, replaced functions refer to the config.angles value when deciding as what to interpret the input (or in case of the arcus functions, to what to convert their result before returning it).

CherryDT
  • 25,571
  • 5
  • 49
  • 74
  • Would it be possible to instead create new functions for the degree versions? For example: sindeg() would be sin() but for degrees. – OIRNOIR Jan 13 '22 at 18:14
  • Sure, just give them different names (and then you don't need the `override` switch anymore), and you don't need to save the old function instances, you can directly access them from the `math` object, since you never override them. And of course then you'd hardcode the degree mode into them instead of referring to the configuration object the example was using. – CherryDT Jan 13 '22 at 18:14