Javascript как найти формула

Current answers are either:

  • suggesting huge external libraries
  • using eval('...') or Function('...') which is bad
  • working only with +, -
  • failing at operator precedence (e.g 1+2*3 should return 7 not 9) when they implement * and/or /

Here an implementation (270 lines of code) that replaces eval.

It supports +, -, *, /, %, ^, parentheses and functions (min, max, sin, cos, tan, log). You can also easily add support for more functions like sqrt, asin, acos…)

It uses TypeScript, is documented and tested.

It internally uses the shunting yard algorithm and reverse Polish notation.

// WTF!
// parseFloat('-0') => -0 vs parseFloat(-0) => 0
// -0 === 0 => true vs Object.is(-0, 0) => false
const minus0Hack = (value: number) => (Object.is(value, -0) ? '-0' : value);

export const operators: {
  [operator: string]:
    | {
        func: (...args: string[]) => string;
        precedence: number;
        associativity: 'left' | 'right';
        arity: number; // Needed by evalReversePolishNotation()
      }
    | undefined;
} = {
  '+': {
    func: (x, y) => `${minus0Hack(Number(x) + Number(y))}`,
    precedence: 1,
    associativity: 'left',
    arity: 2
  },
  '-': {
    func: (x, y) => `${minus0Hack(Number(x) - Number(y))}`,
    precedence: 1,
    associativity: 'left',
    arity: 2
  },
  '*': {
    func: (x, y) => `${minus0Hack(Number(x) * Number(y))}`,
    precedence: 2,
    associativity: 'left',
    arity: 2
  },
  '/': {
    func: (x, y) => `${minus0Hack(Number(x) / Number(y))}`,
    precedence: 2,
    associativity: 'left',
    arity: 2
  },
  '%': {
    func: (x, y) => `${minus0Hack(Number(x) % Number(y))}`,
    precedence: 2,
    associativity: 'left',
    arity: 2
  },
  '^': {
    // Why Math.pow() instead of **?
    // -2 ** 2 => "SyntaxError: Unary operator used immediately before exponentiation expression..."
    // Math.pow(-2, 2) => -4
    // eslint-disable-next-line prefer-exponentiation-operator, no-restricted-properties
    func: (x, y) => `${minus0Hack(Math.pow(Number(x), Number(y)))}`,
    precedence: 3,
    associativity: 'right',
    arity: 2
  }
};
export const operatorsKeys = Object.keys(operators);

export const functions: {
  [operator: string]:
    | {
        func: (...args: string[]) => string;
        // Needed by evalReversePolishNotation()
        arity: number;
      }
    | undefined;
} = {
  min: { func: (x, y) => `${minus0Hack(Math.min(Number(x), Number(y)))}`, arity: 2 },
  max: { func: (x, y) => `${minus0Hack(Math.max(Number(x), Number(y)))}`, arity: 2 },
  sin: { func: x => `${minus0Hack(Math.sin(Number(x)))}`, arity: 1 },
  cos: { func: x => `${minus0Hack(Math.cos(Number(x)))}`, arity: 1 },
  tan: { func: x => `${minus0Hack(Math.tan(Number(x)))}`, arity: 1 },
  log: { func: x => `${Math.log(Number(x))}`, arity: 1 } // No need for -0 hack
};
export const functionsKeys = Object.keys(functions);

const top = (stack: string[]): string | undefined => stack[stack.length - 1];

/**
 * Shunting yard algorithm: converts infix expression to postfix expression (reverse Polish notation)
 *
 * Example: ['1', '+', '2'] => ['1', '2', '+']
 *
 * https://en.wikipedia.org/wiki/Shunting_yard_algorithm
 * https://github.com/poteat/shunting-yard-typescript
 * https://blog.kallisti.net.nz/2008/02/extension-to-the-shunting-yard-algorithm-to-allow-variable-numbers-of-arguments-to-functions/
 */
export function shuntingYard(tokens: string[]) {
  const output = new Array<string>();
  const operatorStack = new Array<string>();

  for (const token of tokens) {
    if (functions[token] !== undefined) {
      operatorStack.push(token);
    } else if (token === ',') {
      while (operatorStack.length > 0 && top(operatorStack) !== '(') {
        output.push(operatorStack.pop()!);
      }
      if (operatorStack.length === 0) {
        throw new Error("Misplaced ','");
      }
    } else if (operators[token] !== undefined) {
      const o1 = token;
      while (
        operatorStack.length > 0 &&
        top(operatorStack) !== undefined &&
        top(operatorStack) !== '(' &&
        (operators[top(operatorStack)!]!.precedence > operators[o1]!.precedence ||
          (operators[o1]!.precedence === operators[top(operatorStack)!]!.precedence &&
            operators[o1]!.associativity === 'left'))
      ) {
        output.push(operatorStack.pop()!); // o2
      }
      operatorStack.push(o1);
    } else if (token === '(') {
      operatorStack.push(token);
    } else if (token === ')') {
      while (operatorStack.length > 0 && top(operatorStack) !== '(') {
        output.push(operatorStack.pop()!);
      }
      if (operatorStack.length > 0 && top(operatorStack) === '(') {
        operatorStack.pop();
      } else {
        throw new Error('Parentheses mismatch');
      }
      if (functions[top(operatorStack)!] !== undefined) {
        output.push(operatorStack.pop()!);
      }
    } else {
      output.push(token);
    }
  }

  // Remaining items
  while (operatorStack.length > 0) {
    const operator = top(operatorStack);
    if (operator === '(') {
      throw new Error('Parentheses mismatch');
    } else {
      output.push(operatorStack.pop()!);
    }
  }

  return output;
}

/**
 * Evaluates reverse Polish notation (RPN) (postfix expression).
 *
 * Example: ['1', '2', '+'] => 3
 *
 * https://en.wikipedia.org/wiki/Reverse_Polish_notation
 * https://github.com/poteat/shunting-yard-typescript
 */
export function evalReversePolishNotation(tokens: string[]) {
  const stack = new Array<string>();

  const ops = { ...operators, ...functions };

  for (const token of tokens) {
    const op = ops[token];

    // eslint-disable-next-line unicorn/no-negated-condition
    if (op !== undefined) {
      const parameters = [];
      for (let i = 0; i < op.arity; i++) {
        parameters.push(stack.pop()!);
      }
      stack.push(op.func(...parameters.reverse()));
    } else {
      stack.push(token);
    }
  }

  if (stack.length > 1) {
    throw new Error('Insufficient operators');
  }

  return Number(stack[0]);
}

/**
 * Breaks a mathematical expression into tokens.
 *
 * Example: "1 + 2" => [1, '+', 2]
 *
 * https://gist.github.com/tchayen/44c28e8d4230b3b05e9f
 */
export function tokenize(expression: string) {
  // "1  +" => "1 +"
  const expr = expression.replace(/s+/g, ' ');

  const tokens = [];

  let acc = '';
  let currentNumber = '';

  for (let i = 0; i < expr.length; i++) {
    const c = expr.charAt(i);
    const prev_c = expr.charAt(i - 1); // '' if index out of range
    const next_c = expr.charAt(i + 1); // '' if index out of range

    const lastToken = top(tokens);

    const numberParsingStarted = currentNumber !== '';

    if (
      // 1
      /d/.test(c) ||
      // Unary operator: +1 or -1
      ((c === '+' || c === '-') &&
        !numberParsingStarted &&
        (lastToken === undefined ||
          lastToken === ',' ||
          lastToken === '(' ||
          operatorsKeys.includes(lastToken)) &&
        /d/.test(next_c))
    ) {
      currentNumber += c;
    } else if (c === '.') {
      if (numberParsingStarted && currentNumber.includes('.')) {
        throw new Error(`Double '.' in number: '${currentNumber}${c}'`);
      } else {
        currentNumber += c;
      }
    } else if (c === ' ') {
      if (/d/.test(prev_c) && /d/.test(next_c)) {
        throw new Error(`Space in number: '${currentNumber}${c}${next_c}'`);
      }
    } else if (functionsKeys.includes(acc + c)) {
      acc += c;
      if (!functionsKeys.includes(acc + next_c)) {
        tokens.push(acc);
        acc = '';
      }
    } else if (operatorsKeys.includes(c) || c === '(' || c === ')' || c === ',') {
      if (
        operatorsKeys.includes(c) &&
        !numberParsingStarted &&
        operatorsKeys.includes(lastToken!)
      ) {
        throw new Error(`Consecutive operators: '${lastToken!}${c}'`);
      }
      if (numberParsingStarted) {
        tokens.push(currentNumber);
      }
      tokens.push(c);
      currentNumber = '';
    } else {
      acc += c;
    }
  }

  if (acc !== '') {
    throw new Error(`Invalid characters: '${acc}'`);
  }

  // Add last number to the tokens
  if (currentNumber !== '') {
    tokens.push(currentNumber);
  }

  // ['+', '1'] => ['0', '+', '1']
  // ['-', '1'] => ['0', '-', '1']
  if (tokens[0] === '+' || tokens[0] === '-') {
    tokens.unshift('0');
  }

  return tokens;
}

export function calculate(expression: string) {
  const tokens = tokenize(expression);
  const rpn = shuntingYard(tokens);
  return evalReversePolishNotation(rpn);
}

The most important part, the unit tests:

/* eslint-disable no-eval, unicorn/prefer-number-properties */

import { calculate, evalReversePolishNotation, shuntingYard, tokenize } from './calculator';
import { convertMathExpressionToEval, getRandomMathExpression } from './getRandomMathExpression';
import { getRandomInt } from './getRandomNumber';

test('shuntingYard()', () => {
  {
    // https://en.wikipedia.org/wiki/Shunting_yard_algorithm#Detailed_examples
    const rpn = shuntingYard('3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3'.split(' '));
    expect(rpn).toEqual(['3', '4', '2', '*', '1', '5', '-', '2', '3', '^', '^', '/', '+']);
  }

  {
    // https://en.wikipedia.org/wiki/Shunting_yard_algorithm#Detailed_examples
    const rpn = shuntingYard('sin ( max ( 2 3 ) / 3 * 3.14 )'.split(' '));
    expect(rpn).toEqual(['2', '3', 'max', '3', '/', '3.14', '*', 'sin']);
  }

  // Parentheses mismatch
  expect(() => shuntingYard(['('])).toThrow('Parentheses mismatch');
  expect(() => shuntingYard([')'])).toThrow('Parentheses mismatch');
  expect(() => shuntingYard('1 - ( 2 * 3 ) )'.split(' '))).toThrow('Parentheses mismatch');
  expect(() => shuntingYard('1 - ( 2 * 3 ) ) + 4'.split(' '))).toThrow('Parentheses mismatch');

  // Ignore ','
  expect(shuntingYard('max ( 1 , 2 )'.split(' '))).toEqual(['1', '2', 'max']);

  // if the token is: ',':
  //   while the operator at the top of the operator stack is not a left parenthesis:
  //     pop the operator from the operator stack into the output queue
  expect(shuntingYard('max ( 0 + 1 , 2 )'.split(' '))).toEqual(['0', '1', '+', '2', 'max']);

  // Misplaced ','
  expect(() => shuntingYard('1 , 2'.split(' '))).toThrow("Misplaced ','");
  expect(() => shuntingYard(', 1 / 2'.split(' '))).toThrow("Misplaced ','");
  expect(() => shuntingYard('1 , / 2'.split(' '))).toThrow("Misplaced ','");
  expect(() => shuntingYard('1 / , 2'.split(' '))).toThrow("Misplaced ','");
  expect(() => shuntingYard('1 / 2 ,'.split(' '))).toThrow("Misplaced ','");
  expect(() =>
    shuntingYard('sin ( , max , ( , 2 , 3 , ) , / , 3 , * , 3.14 , )'.split(' '))
  ).not.toThrow();

  // Edge cases
  expect(shuntingYard([''])).toEqual(['']);
  expect(shuntingYard([' '])).toEqual([' ']);
  expect(shuntingYard(['1'])).toEqual(['1']);
  expect(shuntingYard(['a'])).toEqual(['a']);
  expect(shuntingYard(['1a'])).toEqual(['1a']);
  expect(shuntingYard(['*'])).toEqual(['*']);
  expect(shuntingYard(['/'])).toEqual(['/']);

  // All together expression
  expect(
    shuntingYard(
      '( ( 3.1 + cos ( -4 ) / 2 ) * max ( -6 , 6 ) ^ sin ( 6 ) * 9 ) / tan ( log ( 8.8 + -2 ) % 7 ) + ( 6 * -1 - min ( 6 , -4.2 ) )'.split(
        ' '
      )
    )
  ).toEqual(
    '3.1 -4 cos 2 / + -6 6 max 6 sin ^ * 9 * 8.8 -2 + log 7 % tan / 6 -1 * 6 -4.2 min - +'.split(
      ' '
    )
  );
});

test('reversePolishNotation()', () => {
  // https://rosettacode.org/wiki/Parsing/RPN_calculator_algorithm#JavaScript
  expect(
    evalReversePolishNotation(['3', '4', '2', '*', '1', '5', '-', '2', '3', '^', '^', '/', '+'])
  ).toEqual(3 + (4 * 2) / (1 - 5) ** (2 ** 3));
  expect(
    evalReversePolishNotation(['3', '4', '2', '*', '1', '5', '-', '2', '3', '^', '^', '/', '+'])
  ).toEqual(3.000_122_070_312_5);

  // https://en.wikipedia.org/wiki/Shunting_yard_algorithm#Detailed_examples
  expect(evalReversePolishNotation(['2', '3', 'max', '3', '/', '3.14', '*', 'sin'])).toEqual(
    Math.sin((Math.max(2, 3) / 3) * 3.14)
  );
  expect(evalReversePolishNotation(['2', '3', 'max', '3', '/', '3.14', '*', 'sin'])).toEqual(
    0.001_592_652_916_486_828_2
  );

  // Edge cases
  expect(evalReversePolishNotation([''])).toEqual(0); // :-(
  expect(evalReversePolishNotation([' '])).toEqual(0); // :-(
  expect(evalReversePolishNotation(['1'])).toEqual(1);
  expect(evalReversePolishNotation(['a'])).toBeNaN();
  expect(evalReversePolishNotation(['1a'])).toBeNaN();
  expect(evalReversePolishNotation(['*'])).toBeNaN();
  expect(evalReversePolishNotation(['/'])).toBeNaN();
  expect(() => evalReversePolishNotation(['1', '2'])).toThrow('Insufficient operators');

  // All together expression
  expect(
    evalReversePolishNotation(
      '3.1 -4 cos 2 / + -6 6 max 6 sin ^ * 9 * 8.8 -2 + log 7 % tan / 6 -1 * 6 -4.2 min - +'.split(
        ' '
      )
    )
  ).toEqual(
    eval(
      '((3.1 + Math.cos(-4) / 2) * Math.max(-6, 6) ** Math.sin(6) * 9) / Math.tan(Math.log(8.8 + -2) % 7) + (6 * -1 - Math.min(6, -4.2))'
    )
  );
});

test('tokenize()', () => {
  // https://en.wikipedia.org/wiki/Shunting_yard_algorithm#Detailed_examples
  expect(tokenize('3 + 4 * 2 / (1 - 5) ^ 2 ^ 3')).toEqual(
    '3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3'.split(' ')
  );

  // https://en.wikipedia.org/wiki/Shunting_yard_algorithm#Detailed_examples
  expect(tokenize('sin(max(2, 3) / 3 * 3.14)')).toEqual(
    'sin ( max ( 2 , 3 ) / 3 * 3.14 )'.split(' ')
  );

  expect(tokenize('1+2')).toEqual(['1', '+', '2']);
  expect(tokenize('min(1,2)')).toEqual(['min', '(', '1', ',', '2', ')']);
  expect(tokenize('1.1+2.2')).toEqual(['1.1', '+', '2.2']);
  expect(tokenize('min(1.1,2.2)')).toEqual(['min', '(', '1.1', ',', '2.2', ')']);

  // Decimals
  expect(tokenize('1.1 + 2.2 - 3.3 * 4.4 / 5.5 % 6.6 ^ 7.7')).toEqual(
    '1.1 + 2.2 - 3.3 * 4.4 / 5.5 % 6.6 ^ 7.7'.split(' ')
  );

  // White spaces
  expect(tokenize('')).toEqual([]);
  expect(tokenize(' ')).toEqual([]);
  expect(tokenize(' 1  +  2 ')).toEqual(['1', '+', '2']);
  expect(tokenize('1 n + n 2')).toEqual(['1', '+', '2']);
  expect(tokenize('1 t + t 2')).toEqual(['1', '+', '2']);

  // Single number
  expect(tokenize('0')).toEqual(['0']);
  expect(tokenize('1')).toEqual(['1']);
  expect(tokenize('-0')).toEqual(['-0']);
  expect(tokenize('-1')).toEqual(['-1']);
  expect(tokenize('(1)')).toEqual(['(', '1', ')']);
  expect(tokenize('(-1)')).toEqual(['(', '-1', ')']);
  expect(tokenize('-(1)')).toEqual(['0', '-', '(', '1', ')']);

  // Starting with +/-
  expect(tokenize('+0')).toEqual(['+0']);
  expect(tokenize('+ 0')).toEqual(['0', '+', '0']);
  expect(tokenize('-0')).toEqual(['-0']);
  expect(tokenize('- 0')).toEqual(['0', '-', '0']);
  expect(tokenize('+1')).toEqual(['+1']);
  expect(tokenize('+ 1')).toEqual(['0', '+', '1']);
  expect(tokenize('-1')).toEqual(['-1']);
  expect(tokenize('- 1')).toEqual(['0', '-', '1']);
  expect(tokenize('+1 + 1')).toEqual(['+1', '+', '1']);
  expect(tokenize('+ 1 + 1')).toEqual(['0', '+', '1', '+', '1']);
  expect(tokenize('-1 + 1')).toEqual(['-1', '+', '1']);
  expect(tokenize('- 1 + 1')).toEqual(['0', '-', '1', '+', '1']);
  expect(tokenize('+')).toEqual(['0', '+']);
  expect(tokenize('-')).toEqual(['0', '-']);

  // Do not confuse '+1' / '-1' with 'x + 1' / 'x - 1' depending on the context
  expect(tokenize('(1+2)+1')).toEqual(['(', '1', '+', '2', ')', '+', '1']);
  expect(tokenize('(1+2)-1')).toEqual(['(', '1', '+', '2', ')', '-', '1']);
  expect(tokenize('1 + -2')).toEqual(['1', '+', '-2']);
  expect(tokenize('1+-2')).toEqual(['1', '+', '-2']);

  // Space in number
  expect(() => tokenize('1 2')).toThrow("Space in number: '1 2'");
  expect(() => tokenize('1  2')).toThrow("Space in number: '1 2'");
  expect(() => tokenize('0 + 1 / (2 3) * 4')).toThrow("Space in number: '2 3'");
  expect(() => tokenize('min(1 2)')).toThrow("Space in number: '1 2'");

  // Double '.' in number
  expect(() => tokenize('1+2.3.4')).toThrow("Double '.' in number: '2.3.'");
  expect(() => tokenize('1+2.3.4.5')).toThrow("Double '.' in number: '2.3.'");
  expect(() => tokenize('0 + 1 / 2.3.4 * 5')).toThrow("Double '.' in number: '2.3.'");
  expect(() => tokenize('min(1, 2.3.4)')).toThrow("Double '.' in number: '2.3.'");

  // Consecutive operators
  expect(tokenize('1++2')).toEqual(['1', '+', '+2']);
  expect(tokenize('1-+2')).toEqual(['1', '-', '+2']);
  expect(tokenize('1--2')).toEqual(['1', '-', '-2']);
  expect(() => tokenize('1++')).toThrow("Consecutive operators: '++'");
  expect(() => tokenize('1-+')).toThrow("Consecutive operators: '-+'");
  expect(() => tokenize('1--')).toThrow("Consecutive operators: '--'");
  expect(() => tokenize('1-*2')).toThrow("Consecutive operators: '-*'");
  expect(() => tokenize('0 + 1 / (2-*3) * 4')).toThrow("Consecutive operators: '-*'");
  expect(() => tokenize('min(1-*2, 3)')).toThrow("Consecutive operators: '-*'");

  // Other edge cases
  expect(tokenize('1,2')).toEqual(['1', ',', '2']);
  expect(tokenize('1+2+')).toEqual(['1', '+', '2', '+']); // :-(
  expect(() => tokenize('1+2a')).toThrow("Invalid characters: 'a'");
  expect(() => tokenize('10 Hello')).toThrow("Invalid characters: 'Hello'");
  expect(tokenize('1-.')).toEqual(['1', '-', '.']); // :-(
  expect(tokenize('*')).toEqual(['*']);
  expect(tokenize('/')).toEqual(['/']);

  // All together expression
  expect(
    tokenize(
      '((3.1 + cos(-4) / 2) * max(-6, 6) ^ sin(6) * 9) / tan(log(8.8 + -2) % 7) + (6 * -1 - min(6, -4.2))'
    )
  ).toEqual(
    '( ( 3.1 + cos ( -4 ) / 2 ) * max ( -6 , 6 ) ^ sin ( 6 ) * 9 ) / tan ( log ( 8.8 + -2 ) % 7 ) + ( 6 * -1 - min ( 6 , -4.2 ) )'.split(
      ' '
    )
  );
  expect(
    tokenize('((3.1+cos(-4)/2)*max(-6,6)^sin(6)*9)/tan(log(8.8+-2)%7)+(6*-1-min(6,-4.2))')
  ).toEqual(
    '( ( 3.1 + cos ( -4 ) / 2 ) * max ( -6 , 6 ) ^ sin ( 6 ) * 9 ) / tan ( log ( 8.8 + -2 ) % 7 ) + ( 6 * -1 - min ( 6 , -4.2 ) )'.split(
      ' '
    )
  );
});

test('calculate()', () => {
  // https://en.wikipedia.org/wiki/Shunting_yard_algorithm#Detailed_examples
  expect(calculate('3 + 4 * 2 / (1 - 5) ^ 2 ^ 3')).toEqual(3.000_122_070_312_5);

  // https://en.wikipedia.org/wiki/Shunting_yard_algorithm#Detailed_examples
  expect(calculate('sin(max(2, 3) / 3 * 3.14)')).toEqual(0.001_592_652_916_486_828_2);

  expect(calculate('1+2')).toEqual(3);
  expect(calculate('min(1,2)')).toEqual(1);
  expect(calculate('1.1+2.2')).toEqual(3.300_000_000_000_000_3);
  expect(calculate('min(1.1,2.2)')).toEqual(1.1);

  // if the token is: ',':
  //   while the operator at the top of the operator stack is not a left parenthesis:
  //     pop the operator from the operator stack into the output queue
  expect(calculate('max(0 + 1, 2)')).toEqual(2);

  // Decimals
  expect(calculate('1.1 + 2.2 - 3.3 * 4.4 / 5.5 % 6.6 ^ 7.7')).toEqual(
    eval('1.1 + 2.2 - 3.3 * 4.4 / 5.5 % 6.6 ** 7.7')
  );

  // White spaces
  expect(calculate('')).toBeNaN();
  expect(calculate(' ')).toBeNaN();
  expect(calculate(' 1  +  2 ')).toEqual(3);
  expect(calculate('1 n + n 2')).toEqual(3);
  expect(calculate('1 t + t 2')).toEqual(3);

  // -0 hack
  expect(calculate('-0 + -0')).toEqual(-0);
  expect(calculate('-0 - 0')).toEqual(-0);
  expect(calculate('0 * -1')).toEqual(-0);
  expect(calculate('0 / -1')).toEqual(-0);
  expect(calculate('-1 % 1')).toEqual(-0);
  expect(calculate('-0 ^ 1')).toEqual(-0);
  expect(calculate('min(-0, 1)')).toEqual(-0);
  expect(calculate('max(-0, -1)')).toEqual(-0);
  expect(calculate('sin(-0)')).toEqual(-0);
  //expect(Math.cos(Math.PI / 2)).toEqual(0);
  expect(calculate('tan(-0)')).toEqual(-0);
  expect(calculate('log(1)')).toEqual(0); // No need for -0 hack

  // Math.pow() vs **
  expect(calculate('-2 ^ 2')).toEqual((-2) ** 2);
  expect(eval('Math.pow(-2, 2)')).toEqual(4);
  expect(() => eval('-2 ** 2')).toThrow(
    'Unary operator used immediately before exponentiation expression.'
  );

  // Infinity/-Infinity
  expect(calculate('1 / 0')).toEqual(Infinity);
  expect(calculate('1 / -0')).toEqual(-Infinity);
  expect(calculate('-1 / 0')).toEqual(-Infinity);
  expect(calculate('1 + 1 / 0')).toEqual(Infinity);
  expect(calculate('1 - 1 / 0')).toEqual(-Infinity);
  expect(calculate('10 ^ 1000')).toEqual(Infinity);
  expect(calculate('0 - 10 ^ 1000')).toEqual(-Infinity);
  expect(calculate('0 ^ -1')).toEqual(Infinity);
  expect(calculate('-0 ^ -1')).toEqual(-Infinity);
  expect(calculate('log(0)')).toEqual(-Infinity);

  // NaN
  expect(calculate('log(-1)')).toBeNaN();
  expect(calculate('-1 ^ 0.1')).toBeNaN();
  expect(calculate('1 % 0')).toBeNaN();
  expect(calculate('1 / 0 * 0')).toBeNaN();

  // Single number
  expect(calculate('0')).toEqual(0);
  expect(calculate('1')).toEqual(1);
  expect(calculate('-0')).toEqual(-0);
  expect(calculate('-1')).toEqual(-1);
  expect(calculate('(1)')).toEqual(1);
  expect(calculate('(-1)')).toEqual(-1);
  expect(calculate('-(1)')).toEqual(-1);

  // Starting with +/-
  expect(calculate('+0')).toEqual(0);
  expect(calculate('+ 0')).toEqual(0);
  expect(calculate('-0')).toEqual(-0);
  expect(calculate('- 0')).toEqual(0);
  expect(calculate('+1')).toEqual(1);
  expect(calculate('+ 1')).toEqual(+1);
  expect(calculate('-1')).toEqual(-1);
  expect(calculate('- 1')).toEqual(-1);
  expect(calculate('+1 + 1')).toEqual(2);
  expect(calculate('+ 1 + 1')).toEqual(2);
  expect(calculate('-1 + 1')).toEqual(0);
  expect(calculate('- 1 + 1')).toEqual(0);
  expect(calculate('+')).toBeNaN();
  expect(calculate('-')).toBeNaN();

  // Do not confuse '+1' / '-1' with 'x + 1' / 'x - 1' depending on the context
  expect(calculate('(1+2)+1')).toEqual(4);
  expect(calculate('(1+2)-1')).toEqual(2);
  expect(calculate('1 + -2')).toEqual(-1);
  expect(calculate('1+-2')).toEqual(-1);

  // Space in number
  expect(() => calculate('1 2')).toThrow("Space in number: '1 2'");
  expect(() => calculate('1  2')).toThrow("Space in number: '1 2'");
  expect(() => calculate('0 + 1 / (2 3) * 4')).toThrow("Space in number: '2 3'");
  expect(() => calculate('min(1 2)')).toThrow("Space in number: '1 2'");

  // Double '.' in number
  expect(() => calculate('1+2.3.4')).toThrow("Double '.' in number: '2.3.'");
  expect(() => calculate('1+2.3.4.5')).toThrow("Double '.' in number: '2.3.'");
  expect(() => calculate('0 + 1 / 2.3.4 * 5')).toThrow("Double '.' in number: '2.3.'");
  expect(() => calculate('min(1, 2.3.4)')).toThrow("Double '.' in number: '2.3.'");

  // Consecutive operators
  expect(calculate('1++2')).toEqual(3);
  expect(calculate('1-+2')).toEqual(-1);
  expect(calculate('1--2')).toEqual(3);
  expect(() => calculate('1++')).toThrow("Consecutive operators: '++'");
  expect(() => calculate('1-+')).toThrow("Consecutive operators: '-+'");
  expect(() => calculate('1--')).toThrow("Consecutive operators: '--'");
  expect(() => calculate('1-*2')).toThrow("Consecutive operators: '-*'");
  expect(() => calculate('0 + 1 / (2-*3) * 4')).toThrow("Consecutive operators: '-*'");
  expect(() => calculate('min(1-*2, 3)')).toThrow("Consecutive operators: '-*'");

  // Misplaced ','
  expect(() => calculate('1,2')).toThrow("Misplaced ','");
  expect(() => calculate(',1/2')).toThrow("Misplaced ','");
  expect(() => calculate('1,/2')).toThrow("Misplaced ','");
  expect(() => calculate('1/,2')).toThrow("Misplaced ','");
  expect(() => calculate('1/2,')).toThrow("Misplaced ','");
  expect(() => calculate('sin(,max,(,2,3,),/,3,*,3.14,)')).toThrow('Insufficient operators');
  expect(calculate('sin(,max(,2,3,),/3,*3.14,)')).toEqual(0.001_592_652_916_486_828_2);

  // Other edge cases
  expect(calculate('1+2+')).toBeNaN();
  expect(() => calculate('1+2a')).toThrow("Invalid characters: 'a'");
  expect(() => calculate('10 Hello')).toThrow("Invalid characters: 'Hello'");
  expect(calculate('1-.')).toBeNaN();
  expect(calculate('*')).toBeNaN();
  expect(calculate('/')).toBeNaN();

  // All together expression
  expect(
    calculate(
      '((3.1 + cos(-4) / 2) * max(-6, 6) ^ sin(6) * 9) / tan(log(8.8 + -2) % 7) + (6 * -1 - min(6, -4.2))'
    )
  ).toEqual(
    eval(
      '((3.1 + Math.cos(-4) / 2) * Math.max(-6, 6) ** Math.sin(6) * 9) / Math.tan(Math.log(8.8 + -2) % 7) + (6 * -1 - Math.min(6, -4.2))'
    )
  );
  expect(
    calculate('((3.1+cos(-4)/2)*max(-6,6)^sin(6)*9)/tan(log(8.8+-2)%7)+(6*-1-min(6,-4.2))')
  ).toEqual(
    eval(
      '((3.1+Math.cos(-4)/2)*Math.max(-6,6)**Math.sin(6)*9)/Math.tan(Math.log(8.8+-2)%7)+(6*-1-Math.min(6,-4.2))'
    )
  );
});

test('calculate() with getRandomMathExpression()', () => {
  for (let i = 0; i < 1000; i++) {
    const expr = getRandomMathExpression(getRandomInt(1, 100));
    expect(calculate(expr)).toEqual(eval(convertMathExpressionToEval(expr)));
  }
});

More here: https://gist.github.com/tkrotoff/b0b1d39da340f5fc6c5e2a79a8b6cec0

Another solution that only supports +, -, *, / without parentheses: https://code.tutsplus.com/tutorials/what-they-didnt-tell-you-about-es5s-array-extras–net-28263 (by Felix Bohm)

function calculate(expression: string) {
  const parts = parse(expression); // Tokenize function to be implemented

  // Build an array with all operations reduced to additions
  const processed = new Array<number>();

  for (let i = 0; i < parts.length; i++) {
    const part = parts[i];
    switch (part) {
      case '+': {
        // Ignore
        break;
      }
      case '-': {
        const rightValue = parts[++i];
        if (typeof rightValue === 'number') {
          processed.push(-1 * rightValue);
        } else {
          processed.push(Number.NaN);
        }
        break;
      }
      case '*': {
        const leftValue = processed.pop();
        const rightValue = parts[++i];
        if (typeof leftValue === 'number' && typeof rightValue === 'number') {
          processed.push(leftValue * rightValue);
        } else {
          processed.push(Number.NaN);
        }
        break;
      }
      case '/': {
        const leftValue = processed.pop();
        const rightValue = parts[++i];
        if (typeof leftValue === 'number' && typeof rightValue === 'number') {
          processed.push(leftValue / rightValue);
        } else {
          processed.push(Number.NaN);
        }
        break;
      }
      default: {
        processed.push(part);
      }
    }
  }

  // Add all numbers and return the result
  return processed.reduce((accumulator, currentValue) => accumulator + currentValue);
}

Как можно вычислять значение математической функции представленную в виде строки?

Например: f(x,y) = 2*x + 5/y

Как можно подставить x и y, затем вычислить значение функции,

задан 8 дек 2018 в 14:06

elossa's user avatar

var f = "f(x,y) = 2*x + 5/y";
var x = 100;
var y = 2.5;

f = f.replace(/f(x,y)s=s/g, ""); //удаляем лишнее
f = f.replace(/x/g, x); // заменяем подстроку x на значение переменной x
f = f.replace(/y/g, y); // тоже самое с y
f = eval(f); // вычисляем значение строкового выражения

console.log(f);

ответ дан 8 дек 2018 в 14:25

Misha Saidov's user avatar

Misha SaidovMisha Saidov

5,3761 золотой знак9 серебряных знаков30 бронзовых знаков

2

Нашел отличную библиотеку для решения задачи “math.js”:

var f = "2*x^2 + sqrt(5/y)";
var fx = 2;
var fy = 5

var node = math.parse(f);
console.log(node.eval({x: fx, y: fy})); 

ответ дан 8 дек 2018 в 16:08

elossa's user avatar

elossaelossa

1491 золотой знак2 серебряных знака14 бронзовых знаков

Встроенный объект JavaScript Math включает ряд полезных функций для выполнения различных математических операций. Давайте погрузимся и посмотрим, как они работают и для чего вы можете их использовать.

Содержание

  1. Math.max и Math.min
  2. Абсолютные значения
  3. Math.pow
  4. Вычисление корней
  5. Логарифмы и экспоненты
  6. Гипотенуза

Math.max и Math.min

Эти функции в значительной степени делают то, что вы ожидаете: они возвращают максимум или минимум списка предоставленных аргументов:

Math.max(1,2,3,4,5)
<< 5

Math.min(4,71,-7,2,1,0)
<< -7

Все аргументы должны быть типа Number данных. В противном случае NaNбудет возвращено:

Math.max('a','b','c')
<< NaN

Math.min(5,"hello",6)
<< NaN

Однако будьте осторожны. JavaScript попытается привести значения к числу:

Math.min(5,true,6)
<< 1

В этом примере логическое значение trueпреобразуется в число 1, поэтому оно возвращается как минимальное значение. Если вы не знакомы с приведением типов, это происходит, когда операнды оператора относятся к разным типам. В этом случае JavaScript попытается преобразовать один операнд в эквивалентное значение типа другого операнда.

В качестве аргумента необходимо указать список чисел, а не массив, но вы можете использовать оператор расширения (…) для распаковки массива чисел:

Math.max(...[8,4,2,1])
<< 8

Функция Math.maxполезна для поиска рекорда из списка результатов, сохраненных в массиве:

const scores = [23,12,52,6,25,38,19,37,76,54,24]
const highScore = Math.max(...scores)
<< 76

Функция Math.minполезна для поиска лучшей цены на сайте сравнения цен:

const prices = [19.99, 20.25, 18.57, 19,75, 25, 22.50]
const bestPrice = Math.min(...prices)
<< 18.57

Абсолютные значения

Абсолютное значение — это просто размер числа, независимо от его размера. Это означает, что положительные числа остаются прежними, а отрицательные теряют знак минус. Функция Math.absвычислит абсолютное значение своего аргумента:

Math.abs(5)
<< 5

Math.abs(-42)
<< 42

Math.abs(-3.14159)
<< 3.14159

Почему вы хотите это сделать? Ну, иногда вы хотите вычислить разницу между двумя значениями, которую вы вычисляете, вычитая наименьшее из наибольшего, но часто вы не будете знать заранее, какое из двух значений наименьшее. Чтобы обойти это, вы можете просто вычесть числа в любом порядке и взять абсолютное значение:

const x = 5
const y = 8

const difference = Math.abs(x - y)
<< 3

Практический пример может быть на веб-сайте для экономии денег, где вы хотите знать, сколько вы можете сэкономить, рассчитав разницу между двумя сделками, поскольку вы будете иметь дело с данными о ценах в реальном времени и не будете знать заранее, какая сделка была. самый дешевый:

const dealA = 150
const dealB = 167

const saving = Math.abs(dealA - dealB)
<< 17

Math.pow

Math.powвыполняет расчеты мощности, например:

3= 81

В приведенном выше примере число 3 известно как основание, а 4 — как показатель степени. Мы бы прочитали это как «3 в степени 4 равно 81».

Функция принимает два значения — основание и показатель степени — и возвращает результат возведения основания в степень степени:

Math.pow(2,3)
<< 8

Math.pow(8,0)
<< 1

Math.pow(-1,-1)
<< -1

Math.powв значительной степени был заменен инфиксным оператором возведения в степень ( **), введенным в ES2016, который выполняет точно такую ​​же операцию:

2 ** 3
<< 8

8 ** 0
<< 1

(-1) ** (-1)
<< -1

Вычисление корней

Корни — это операция, обратная степеням. Например, поскольку 3 в квадрате равно 9, квадратный корень из 9 равен 3.

Math.sqrtможет использоваться для возврата квадратного корня числа, предоставленного в качестве аргумента:

Math.sqrt(4)
<< 2

Math.sqrt(100)
<< 10

Math.sqrt(2)
<< 1.4142135623730951

Эта функция вернется NaN, если в качестве аргумента будет указано отрицательное число или нечисловое значение:

Math.sqrt(-1)
<< NaN

Math.sqrt("four")
<< NaN

Но будьте осторожны, потому что JavaScript попытается принудить тип:

Math.sqrt('4') 
<< 2

Math.sqrt(true)
<< 1

Math.cbrtвозвращает кубический корень числа. Это принимает все числа, включая отрицательные числа. Он также попытается принудить тип, если используется значение, не являющееся числом. Если он не может привести значение к числу, он вернет NaN:

Math.cbrt(1000)
<< 10

Math.cbrt(-1000)
<< -10

Math.cbrt("10")
<< 2.154434690031884

Math.cbrt(false)
<< 0

Другие корни можно вычислить, используя оператор возведения в степень и дробную степень. Например, корень четвертой степени можно найти, возведя его в четверть степени (или 0,25). Таким образом, следующий код вернет корень четвертой степени из 625:

625 ** 0.25
<< 5

Чтобы найти корень пятой степени числа, вы должны возвести его в степень одной пятой (или 0,2):

32 ** 0.2
<< 2

В общем, чтобы найти корень n-й степени числа, вы должны возвести его в степень 1/n, поэтому, чтобы найти корень шестой степени из миллиона, вы должны возвести его в степень 1/6:

1000000 ** (1/6)
<< 9.999999999999998

Обратите внимание, что здесь есть ошибка округления, так как ответ должен быть ровно 10. Это часто происходит с дробными степенями, которые не могут быть точно выражены в двоичном виде.

Также обратите внимание, что вы не можете найти корни отрицательных чисел, если корень четный. Это вернется NaN. Таким образом, вы не можете попытаться найти, например, 10-й корень из −7 (потому что 10 четно):

(-7) ** 0.1 // 0.1 is 1/10
<< NaN

Одна из причин, по которой вы можете захотеть вычислить корни, — это вычислить темпы роста. Например, вы хотите увеличить свою прибыль в 10 раз к концу года. Насколько ваша прибыль должна расти каждый месяц? Чтобы выяснить это, вам нужно вычислить 12-й корень из 10 или 10 в двенадцатой степени:

10 ** (1/12)
<< 1.2115276586285884

Этот результат говорит нам о том, что месячный коэффициент роста должен составлять около 1,21, чтобы к концу года увеличить прибыль в 10 раз. Или, другими словами, вам нужно увеличивать свою прибыль на 21% каждый месяц, чтобы достичь своей цели.

Логарифмы и экспоненты

Логарифмы — или для краткости журналы — можно использовать для нахождения показателя степени вычисления. Например, представьте, что вы хотите решить следующее уравнение:

2ˣ = 100

В приведенном выше уравнении, xконечно, не целое число, потому что 100 не является степенью числа 2. Это можно решить, используя логарифмы по основанию 2:

x = log²(100) = 6.64 (rounded to 2 d.p.)

У Mathобъекта есть log2метод, который будет выполнять этот расчет:

Math.log2(100)
<< 6.643856189774724

У него также есть log10метод, который выполняет те же вычисления, но использует 10 в качестве базового числа:

Math.log10(100)
<< 2

Этот результат говорит нам о том, что для получения 100 нужно возвести 10 в степень 2.

Есть еще один метод журнала, который просто Math.log. Это вычисляет натуральный логарифм, который использует число Эйлера ( eпримерно 2,7) в качестве основания. Это значение может показаться странным для использования, но на самом деле оно часто встречается в природе, когда происходит экспоненциальный рост — отсюда и название «натуральные логарифмы»:

Math.log(10)
<< 4.605170185988092

Math.log(Math.E)
<< 1

Последнее вычисление показывает, что число Эйлера ( e), хранящееся как константа Math.E, необходимо возвести в степень 1, чтобы получить само себя. Это имеет смысл, потому что любое число в степени 1 на самом деле является самим собой. Те же результаты могут быть получены, если 2 и 10 будут переданы в качестве аргументов для Math.log2и Math.log10:

Math.log2(2)
<< 1

Math.log10(10)
<< 1

Зачем тебе логарифмы? При работе с данными, которые растут экспоненциально, обычно используют логарифмическую шкалу, чтобы легче было увидеть скорость роста. Логарифмические шкалы часто использовались для измерения количества ежедневных случаев COVID-19 во время пандемии, поскольку они так быстро росли.

Если вам посчастливилось иметь веб-сайт, популярность которого быстро растет (скажем, удваивается каждый день), вы можете рассмотреть возможность использования логарифмической шкалы, прежде чем отображать график, показывающий, как растет ваша популярность.

Гипотенуза

Возможно, вы помните, как в школе изучали теорему Пифагора. Это говорит о том, что длину наибольшей стороны прямоугольного треугольника ( гипотенузу ) можно найти по следующей формуле:

= x² + y²

Здесь x и y — длины двух других сторон.

У Mathобъекта есть hypotметод, который будет вычислять длину гипотенузы при наличии двух других длин в качестве аргументов. Например, если одна сторона имеет длину 3, а другая — 4, мы можем вычислить гипотенузу, используя следующий код:

Math.hypot(3,4)
<< 5

Но почему это когда-либо может быть полезно? Итак, гипотенуза — это мера кратчайшего расстояния между двумя точками. Это означает, что если вы знаете координаты x и y двух элементов на странице. Вы можете использовать эту функцию, чтобы вычислить, насколько далеко они друг от друга:

const ship = {x: 220, y: 100}
const boat = {x: 340, y: 50}

const distance = Math.hypot(ship.x - boat.x,ship.y - boat.y)

Я надеюсь, что этот краткий обзор был полезен и помог вам использовать всю мощь объекта JavaScript Math в ваших проектах.

Math — это встроенный объект, который имеет свойства и методы для математических констант и функций. Это не функциональный объект.

Math работает с Number типом. Это не работает с BigInt .

Description

В отличие от многих других глобальных объектов, Math не является конструктором. Все свойства и методы Math являются статическими. Вы ссылаетесь на константу pi как Math.PI и вызываете функцию синуса как Math.sin(x) , где x — аргумент метода. Константы определяются с полной точностью действительных чисел в JavaScript.

Примечание. Многие Math функции имеют точность, зависящую от реализации.

Это означает,что разные браузеры могут давать разные результаты.Даже один и тот же движок JavaScript на разных ОС или архитектуре может давать разные результаты!

Static properties

Math.E

Постоянная Эйлера и основание натуральных логарифмов; приблизительно 2.718 .

Math.LN2

Натуральный логарифм 2 ; приблизительно 0.693 .

Math.LN10

Натуральный логарифм 10 ; приблизительно 2.303 .

Math.LOG2E

Логарифм E по основанию 2 ; примерно 1.443 .

Math.LOG10E

Логарифм E по основанию 10 ; примерно 0.434 .

Math.PI

Отношение длины окружности к ее диаметру; приблизительно 3.14159 .

Math.SQRT1_2

Квадратный корень из ½; приблизительно 0.707 .

Math.SQRT2

Корень квадратный из 2 ; примерно 1.414 .

Static methods

Math.abs()

Возвращает абсолютное значение x .

Math.acos()

Возвращает арккосинус x .

Math.acosh()

Возвращает гиперболический арккосинус x .

Math.asin()

Возвращает арксинус x .

Math.asinh()

Возвращает гиперболический арксин числа.

Math.atan()

Возвращает арктангенс x .

Math.atanh()

Возвращает гиперболический арктангенс x .

Math.atan2()

Возвращает ароктангенс кванта его аргументов.

Math.cbrt()

Возвращает кубический корень из x .

Math.ceil()

Возвращает наименьшее целое число, большее или равное x .

Math.clz32()

Возвращает количество начальных нулевых битов 32-разрядного целого числа x .

Math.cos()

Возвращает косинус x .

Math.cosh()

Возвращает гиперболический косинус x .

Math.exp()

Возвращает e x , где x — аргумент, а e — константа Эйлера ( 2.718 …, основание натурального логарифма).

Math.expm1()

Возвращает вычитание 1 из exp(x) .

Math.floor()

Возвращает наибольшее целое число, меньшее или равное x .

Math.fround()

Возвращает ближайшее представление x с плавающей запятой одинарной точности .

Math.hypot()

Возвращает квадратный корень суммы квадратов его аргументов.

Math.imul()

Возвращает результат 32-битного целочисленного умножения x и y .

Math.log()

Возвращает натуральный логарифм (㏒ e ; также ㏑) числа x .

Math.log1p()

Возвращает натуральный логарифм (㏒ e ; также ㏑) 1 + x для числа x .

Math.log10()

Возвращает десятичный логарифм числа x .

Math.log2()

Возвращает логарифм x по основанию 2 .

Math.max()

Возвращает наибольшее из нулей и более чисел.

Math.min()

Возвращает наименьшее из нулей и более чисел.

Math.pow()

Возвращает основание x в степень степени y (то есть x y ).

Math.random()

Возвращает псевдослучайное число от 0 до 1 .

Math.round()

Возвращает значение числа x , округленное до ближайшего целого числа.

Math.sign()

Возвращает знак x , указывающий, является ли x положительным, отрицательным или нулем.

Math.sin()

Возвращает синус x .

Math.sinh()

Возвращает гиперболический синус x .

Math.sqrt()

Возвращает положительный квадратный корень из x .

Math.tan()

Возвращает тангенс x .

Math.tanh()

Возвращает гиперболический тангенс x .

Math.trunc()

Возвращает целую часть x , удаляя все цифры дробной части.

Examples

Преобразование между градусами и радианами

Тригонометрические функции sin() , cos() , tan() , asin() , acos() , atan() и atan2() ожидают (и возвращают) углы в радианах .

Поскольку люди склонны мыслить в градусах,а некоторые функции (например,CSS-преобразования)могут принимать градусы,неплохо иметь под рукой функции,преобразующие одно в другое:

function degToRad(degrees) {
  return degrees * (Math.PI / 180);
}

function radToDeg(rad) {
  return rad / (Math.PI / 180);
}

Вычисление высоты равностороннего треугольника

Если мы хотим вычислить высоту равностороннего треугольника и знаем, что длина его стороны равна 100, мы можем использовать формулу: длина смежного треугольника, умноженная на тангенс угла, равна противоположному.

An equilateral triangle where a perpendicular of one edge is drawn from the opposite vertex, forming a right triangle with three sides marked as "adjacent", "opposite", and "hypotenuse". The angle between the "adjacent" and "hypotenuse" sides is 60 degrees.

В JavaScript мы можем сделать это следующим образом:

50 * Math.tan(degToRad(60))

Мы используем нашу degToRad() для преобразования 60 градусов в радианы, поскольку Math.tan() ожидает входное значение в радианах.

Возвращение случайного целого числа между двумя границами

Этого можно добиться с помощью комбинации Math.random() и Math.floor() :

function random(min, max) {
  const num = Math.floor(Math.random() * (max - min + 1)) + min;
  return num;
}

random(1, 10);

Specifications

Browser compatibility

Desktop Mobile Server
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox для Android Opera Android Safari на IOS Samsung Internet Deno Node.js
E

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

LN10

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

LN2

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

LOG10E

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

LOG2E

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

PI

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

SQRT1_2

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

SQRT2

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

Math

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

abs

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

acos

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

acosh

38

12

25

No

25

8

38

38

25

25

8

3.0

1.0

0.12.0

asin

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

asinh

38

12

25

No

25

8

38

38

25

25

8

3.0

1.0

0.12.0

atan

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

atan2

1

12

1

4

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

atanh

38

12

25

No

25

8

38

38

25

25

8

3.0

1.0

0.12.0

cbrt

38

12

25

No

25

8

38

38

25

25

8

3.0

1.0

0.12.0

ceil

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

clz32

38

12

31

No

25

7

38

38

31

25

7

3.0

1.0

0.12.0

cos

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

cosh

38

12

25

No

25

8

38

38

25

25

8

3.0

1.0

0.12.0

exp

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

expm1

38

12

25

No

25

8

38

38

25

25

8

3.0

1.0

0.12.0

floor

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

fround

38

12

26

No

25

8

38

38

26

25

8

3.0

1.0

0.12.0

hypot

38

12

27

No

25

8

38

38

27

25

8

3.0

1.0

0.12.0

imul

28

12

20

No

16

7

4.4

28

20

15

7

1.5

1.0

0.12.0

log

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

log10

38

12

25

No

25

8

38

38

25

25

8

3.0

1.0

0.12.0

log1p

38

12

25

No

25

8

38

38

25

25

8

3.0

1.0

0.12.0

log2

38

12

25

No

25

8

38

38

25

25

8

3.0

1.0

0.12.0

max

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

min

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

pow

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

random

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

round

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

sign

38

12

25

No

25

9

38

38

25

25

9

3.0

1.0

0.12.0

sin

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

sinh

38

12

25

No

25

8

38

38

25

25

8

3.0

1.0

0.12.0

sqrt

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

tan

1

12

1

3

3

1

4.4

18

4

10.1

1

1.0

1.0

0.10.0

tanh

38

12

25

No

25

8

38

38

25

25

8

3.0

1.0

0.12.0

trunc

38

12

25

No

25

8

38

38

25

25

8

3.0

1.0

0.12.0

See also

  • Number


JavaScript

  • Map.prototype.size

    Свойство доступа size возвращает количество элементов в объекте Map.

  • Map.prototype.values()

    Метод values()возвращает новый объект-итератор,который содержит для каждого элемента Map порядок вставки.

  • Math.abs()

    Функция Math.abs()возвращает абсолютное значение числа.

  • Math.acos()

    Функция Math.acos()возвращает арккосинус радиан)числа,то есть x 1 Math.acos arccos единственное y 0 π такое,что forall in [{-1};1],;mathtt{operatorname{Math.acos}(x)}



Как уже было сказано, в javascript определены некоторые стандартные объекты и функции. Сегодня мы рассмотрим объект
math и его методы.

Объект math работает с математическими функциями, а его методы можно использовать для вызова этих функций.
Ниже представлены некоторые методы объекта math:

метод описание
abs абсолютное значение
sin, cos, tan тригонометрические функции
log натуральный логарифм
exp экспонента
pow показательная функция
sqrt квадратный корень
min наименьшее значение
max наибольшее значение

Какие-то методы вы будете использовать часто, какие-то редко, а некоторые вам и вовсе не пригодятся.
Рассмотрим пример использования объекта math.

Предположим мы хотим написать сценарий, который будет вычислять площадь треугольника по трем его сторонам.
Для этого нам потребуется использовать формулу Герона:

формула Герона

Для тех, кто давно окончил школу, напоминаю:
S – площадь
a, b, c – длины сторон треугольника

Итак, напишем в html-странице код формы:

math javascript

Сторона 1
Сторона 2
Сторона 3

Результат

Здесь нет ничего нового. Теперь на странице script.js напишем код функции:

function areaOfTriangle(obj){
var a=1*obj.st1.value;
var b=1*obj.st2.value;
var c=1*obj.st3.value;
var p=(a+b+c)/2;
var s=Math.sqrt(p*(p-a)*(p-b)*(p-c));
obj.res.value=s;
}

Здесь мы использовали объект Math и его метод sqrt для извлечения квадратного корня. Выражение, из которого извлекается корень,
должно быть взято в скобки.

Также обратите внимание на первые три строчки функции, они начинаются с 1*, т.е. наши переменные a, b, c мы
умножили на единицу. Зачем? Помните, в первом уроке мы говорили о том, что у каждой переменной есть тип, который определяется
автоматически. Так как наши переменные приходят из текстового поля, то и тип они имеют string, т.е.
они воспринимаются функцией не как числа, а как буквы.

Если бы мы
перемножали эти переменные, то их тип автоматически переопределился бы к number, но мы их складываем, а
знак операции + расценивается в данном случае, как конкатенация строк. Чтобы этого не происходило, мы и
умножили наши переменные на единицу, преобразовав их таким образом к типу number. Ради эксперимента уберите
умножение на единицу в этих трех строках и посмотрите, что “насчитает” сценарий. Затем верните правильный вариант и убедитесь,
что все работает правильно, как в примере ниже.

Вроде все хорошо, но есть один нюанс: если извлекаемый корень является дробным числом, то дробная часть может быть бесконечно
длинной. Для точных вычислений это необходимо, но в большинстве случаев достаточно двух знаков после запятой.

Чтобы округлить результат до N знаков после запятой можно воспользоваться методом toFixed объекта
Number. Синтаксис записи следующий:

(x).toFixed(N)

где x – число, которое надо округлить, а N – число знаков после запятой.

Давайте исправим конец нашей функции и округлим результат до 2 знаков после запятой:

function areaOfTriangle(obj){
var a=1*obj.st1.value;
var b=1*obj.st2.value;
var c=1*obj.st3.value;
var p=(a+b+c)/2;
var s=Math.sqrt(p*(p-a)*(p-b)*(p-c));
s=s.toFixed(2);
obj.res.value=s;
}

Вот теперь все аккуратненько:

Вот на сегодня и все, для тренировки поэкспериментируйте с другими методами объекта math.

Предыдущий урок
Вернуться в раздел
Следующий урок

Добавить комментарий