math.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. /**
  2. * @license
  3. * Visual Blocks Language
  4. *
  5. * Copyright 2012 Google Inc.
  6. * https://developers.google.com/blockly/
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. /**
  21. * @fileoverview Generating JavaScript for math blocks.
  22. * @author q.neutron@gmail.com (Quynh Neutron)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.JavaScript.math');
  26. goog.require('Blockly.JavaScript');
  27. Blockly.JavaScript['math_number'] = function(block) {
  28. // Numeric value.
  29. var code = parseFloat(block.getFieldValue('NUM'));
  30. return [code, Blockly.JavaScript.ORDER_ATOMIC];
  31. };
  32. Blockly.JavaScript['math_arithmetic'] = function(block) {
  33. // Basic arithmetic operators, and power.
  34. var OPERATORS = {
  35. 'ADD': [' + ', Blockly.JavaScript.ORDER_ADDITION],
  36. 'MINUS': [' - ', Blockly.JavaScript.ORDER_SUBTRACTION],
  37. 'MULTIPLY': [' * ', Blockly.JavaScript.ORDER_MULTIPLICATION],
  38. 'DIVIDE': [' / ', Blockly.JavaScript.ORDER_DIVISION],
  39. 'POWER': [null, Blockly.JavaScript.ORDER_COMMA] // Handle power separately.
  40. };
  41. var tuple = OPERATORS[block.getFieldValue('OP')];
  42. var operator = tuple[0];
  43. var order = tuple[1];
  44. var argument0 = Blockly.JavaScript.valueToCode(block, 'A', order) || '0';
  45. var argument1 = Blockly.JavaScript.valueToCode(block, 'B', order) || '0';
  46. var code;
  47. // Power in JavaScript requires a special case since it has no operator.
  48. if (!operator) {
  49. code = 'Math.pow(' + argument0 + ', ' + argument1 + ')';
  50. return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
  51. }
  52. code = argument0 + operator + argument1;
  53. return [code, order];
  54. };
  55. Blockly.JavaScript['math_single'] = function(block) {
  56. // Math operators with single operand.
  57. var operator = block.getFieldValue('OP');
  58. var code;
  59. var arg;
  60. if (operator == 'NEG') {
  61. // Negation is a special case given its different operator precedence.
  62. arg = Blockly.JavaScript.valueToCode(block, 'NUM',
  63. Blockly.JavaScript.ORDER_UNARY_NEGATION) || '0';
  64. if (arg[0] == '-') {
  65. // --3 is not legal in JS.
  66. arg = ' ' + arg;
  67. }
  68. code = '-' + arg;
  69. return [code, Blockly.JavaScript.ORDER_UNARY_NEGATION];
  70. }
  71. if (operator == 'SIN' || operator == 'COS' || operator == 'TAN') {
  72. arg = Blockly.JavaScript.valueToCode(block, 'NUM',
  73. Blockly.JavaScript.ORDER_DIVISION) || '0';
  74. } else {
  75. arg = Blockly.JavaScript.valueToCode(block, 'NUM',
  76. Blockly.JavaScript.ORDER_NONE) || '0';
  77. }
  78. // First, handle cases which generate values that don't need parentheses
  79. // wrapping the code.
  80. switch (operator) {
  81. case 'ABS':
  82. code = 'Math.abs(' + arg + ')';
  83. break;
  84. case 'ROOT':
  85. code = 'Math.sqrt(' + arg + ')';
  86. break;
  87. case 'LN':
  88. code = 'Math.log(' + arg + ')';
  89. break;
  90. case 'EXP':
  91. code = 'Math.exp(' + arg + ')';
  92. break;
  93. case 'POW10':
  94. code = 'Math.pow(10,' + arg + ')';
  95. break;
  96. case 'ROUND':
  97. code = 'Math.round(' + arg + ')';
  98. break;
  99. case 'ROUNDUP':
  100. code = 'Math.ceil(' + arg + ')';
  101. break;
  102. case 'ROUNDDOWN':
  103. code = 'Math.floor(' + arg + ')';
  104. break;
  105. case 'SIN':
  106. code = 'Math.sin(' + arg + ' / 180 * Math.PI)';
  107. break;
  108. case 'COS':
  109. code = 'Math.cos(' + arg + ' / 180 * Math.PI)';
  110. break;
  111. case 'TAN':
  112. code = 'Math.tan(' + arg + ' / 180 * Math.PI)';
  113. break;
  114. }
  115. if (code) {
  116. return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
  117. }
  118. // Second, handle cases which generate values that may need parentheses
  119. // wrapping the code.
  120. switch (operator) {
  121. case 'LOG10':
  122. code = 'Math.log(' + arg + ') / Math.log(10)';
  123. break;
  124. case 'ASIN':
  125. code = 'Math.asin(' + arg + ') / Math.PI * 180';
  126. break;
  127. case 'ACOS':
  128. code = 'Math.acos(' + arg + ') / Math.PI * 180';
  129. break;
  130. case 'ATAN':
  131. code = 'Math.atan(' + arg + ') / Math.PI * 180';
  132. break;
  133. default:
  134. throw 'Unknown math operator: ' + operator;
  135. }
  136. return [code, Blockly.JavaScript.ORDER_DIVISION];
  137. };
  138. Blockly.JavaScript['math_constant'] = function(block) {
  139. // Constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY.
  140. var CONSTANTS = {
  141. 'PI': ['Math.PI', Blockly.JavaScript.ORDER_MEMBER],
  142. 'E': ['Math.E', Blockly.JavaScript.ORDER_MEMBER],
  143. 'GOLDEN_RATIO':
  144. ['(1 + Math.sqrt(5)) / 2', Blockly.JavaScript.ORDER_DIVISION],
  145. 'SQRT2': ['Math.SQRT2', Blockly.JavaScript.ORDER_MEMBER],
  146. 'SQRT1_2': ['Math.SQRT1_2', Blockly.JavaScript.ORDER_MEMBER],
  147. 'INFINITY': ['Infinity', Blockly.JavaScript.ORDER_ATOMIC]
  148. };
  149. return CONSTANTS[block.getFieldValue('CONSTANT')];
  150. };
  151. Blockly.JavaScript['math_number_property'] = function(block) {
  152. // Check if a number is even, odd, prime, whole, positive, or negative
  153. // or if it is divisible by certain number. Returns true or false.
  154. var number_to_check = Blockly.JavaScript.valueToCode(block, 'NUMBER_TO_CHECK',
  155. Blockly.JavaScript.ORDER_MODULUS) || '0';
  156. var dropdown_property = block.getFieldValue('PROPERTY');
  157. var code;
  158. if (dropdown_property == 'PRIME') {
  159. // Prime is a special case as it is not a one-liner test.
  160. var functionName = Blockly.JavaScript.provideFunction_(
  161. 'math_isPrime',
  162. [ 'function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ + '(n) {',
  163. ' // https://en.wikipedia.org/wiki/Primality_test#Naive_methods',
  164. ' if (n == 2 || n == 3) {',
  165. ' return true;',
  166. ' }',
  167. ' // False if n is NaN, negative, is 1, or not whole.',
  168. ' // And false if n is divisible by 2 or 3.',
  169. ' if (isNaN(n) || n <= 1 || n % 1 != 0 || n % 2 == 0 ||' +
  170. ' n % 3 == 0) {',
  171. ' return false;',
  172. ' }',
  173. ' // Check all the numbers of form 6k +/- 1, up to sqrt(n).',
  174. ' for (var x = 6; x <= Math.sqrt(n) + 1; x += 6) {',
  175. ' if (n % (x - 1) == 0 || n % (x + 1) == 0) {',
  176. ' return false;',
  177. ' }',
  178. ' }',
  179. ' return true;',
  180. '}']);
  181. code = functionName + '(' + number_to_check + ')';
  182. return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
  183. }
  184. switch (dropdown_property) {
  185. case 'EVEN':
  186. code = number_to_check + ' % 2 == 0';
  187. break;
  188. case 'ODD':
  189. code = number_to_check + ' % 2 == 1';
  190. break;
  191. case 'WHOLE':
  192. code = number_to_check + ' % 1 == 0';
  193. break;
  194. case 'POSITIVE':
  195. code = number_to_check + ' > 0';
  196. break;
  197. case 'NEGATIVE':
  198. code = number_to_check + ' < 0';
  199. break;
  200. case 'DIVISIBLE_BY':
  201. var divisor = Blockly.JavaScript.valueToCode(block, 'DIVISOR',
  202. Blockly.JavaScript.ORDER_MODULUS) || '0';
  203. code = number_to_check + ' % ' + divisor + ' == 0';
  204. break;
  205. }
  206. return [code, Blockly.JavaScript.ORDER_EQUALITY];
  207. };
  208. Blockly.JavaScript['math_change'] = function(block) {
  209. // Add to a variable in place.
  210. var argument0 = Blockly.JavaScript.valueToCode(block, 'DELTA',
  211. Blockly.JavaScript.ORDER_ADDITION) || '0';
  212. var varName = Blockly.JavaScript.variableDB_.getName(
  213. block.getFieldValue('VAR'), Blockly.Variables.NAME_TYPE);
  214. return varName + ' = (typeof ' + varName + ' == \'number\' ? ' + varName +
  215. ' : 0) + ' + argument0 + ';\n';
  216. };
  217. // Rounding functions have a single operand.
  218. Blockly.JavaScript['math_round'] = Blockly.JavaScript['math_single'];
  219. // Trigonometry functions have a single operand.
  220. Blockly.JavaScript['math_trig'] = Blockly.JavaScript['math_single'];
  221. Blockly.JavaScript['math_on_list'] = function(block) {
  222. // Math functions for lists.
  223. var func = block.getFieldValue('OP');
  224. var list, code;
  225. switch (func) {
  226. case 'SUM':
  227. list = Blockly.JavaScript.valueToCode(block, 'LIST',
  228. Blockly.JavaScript.ORDER_MEMBER) || '[]';
  229. code = list + '.reduce(function(x, y) {return x + y;})';
  230. break;
  231. case 'MIN':
  232. list = Blockly.JavaScript.valueToCode(block, 'LIST',
  233. Blockly.JavaScript.ORDER_COMMA) || '[]';
  234. code = 'Math.min.apply(null, ' + list + ')';
  235. break;
  236. case 'MAX':
  237. list = Blockly.JavaScript.valueToCode(block, 'LIST',
  238. Blockly.JavaScript.ORDER_COMMA) || '[]';
  239. code = 'Math.max.apply(null, ' + list + ')';
  240. break;
  241. case 'AVERAGE':
  242. // math_median([null,null,1,3]) == 2.0.
  243. var functionName = Blockly.JavaScript.provideFunction_(
  244. 'math_mean',
  245. [ 'function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ +
  246. '(myList) {',
  247. ' return myList.reduce(function(x, y) {return x + y;}) / ' +
  248. 'myList.length;',
  249. '}']);
  250. list = Blockly.JavaScript.valueToCode(block, 'LIST',
  251. Blockly.JavaScript.ORDER_NONE) || '[]';
  252. code = functionName + '(' + list + ')';
  253. break;
  254. case 'MEDIAN':
  255. // math_median([null,null,1,3]) == 2.0.
  256. var functionName = Blockly.JavaScript.provideFunction_(
  257. 'math_median',
  258. [ 'function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ +
  259. '(myList) {',
  260. ' var localList = myList.filter(function (x) ' +
  261. '{return typeof x == \'number\';});',
  262. ' if (!localList.length) return null;',
  263. ' localList.sort(function(a, b) {return b - a;});',
  264. ' if (localList.length % 2 == 0) {',
  265. ' return (localList[localList.length / 2 - 1] + ' +
  266. 'localList[localList.length / 2]) / 2;',
  267. ' } else {',
  268. ' return localList[(localList.length - 1) / 2];',
  269. ' }',
  270. '}']);
  271. list = Blockly.JavaScript.valueToCode(block, 'LIST',
  272. Blockly.JavaScript.ORDER_NONE) || '[]';
  273. code = functionName + '(' + list + ')';
  274. break;
  275. case 'MODE':
  276. // As a list of numbers can contain more than one mode,
  277. // the returned result is provided as an array.
  278. // Mode of [3, 'x', 'x', 1, 1, 2, '3'] -> ['x', 1].
  279. var functionName = Blockly.JavaScript.provideFunction_(
  280. 'math_modes',
  281. [ 'function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ +
  282. '(values) {',
  283. ' var modes = [];',
  284. ' var counts = [];',
  285. ' var maxCount = 0;',
  286. ' for (var i = 0; i < values.length; i++) {',
  287. ' var value = values[i];',
  288. ' var found = false;',
  289. ' var thisCount;',
  290. ' for (var j = 0; j < counts.length; j++) {',
  291. ' if (counts[j][0] === value) {',
  292. ' thisCount = ++counts[j][1];',
  293. ' found = true;',
  294. ' break;',
  295. ' }',
  296. ' }',
  297. ' if (!found) {',
  298. ' counts.push([value, 1]);',
  299. ' thisCount = 1;',
  300. ' }',
  301. ' maxCount = Math.max(thisCount, maxCount);',
  302. ' }',
  303. ' for (var j = 0; j < counts.length; j++) {',
  304. ' if (counts[j][1] == maxCount) {',
  305. ' modes.push(counts[j][0]);',
  306. ' }',
  307. ' }',
  308. ' return modes;',
  309. '}']);
  310. list = Blockly.JavaScript.valueToCode(block, 'LIST',
  311. Blockly.JavaScript.ORDER_NONE) || '[]';
  312. code = functionName + '(' + list + ')';
  313. break;
  314. case 'STD_DEV':
  315. var functionName = Blockly.JavaScript.provideFunction_(
  316. 'math_standard_deviation',
  317. [ 'function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ +
  318. '(numbers) {',
  319. ' var n = numbers.length;',
  320. ' if (!n) return null;',
  321. ' var mean = numbers.reduce(function(x, y) {return x + y;}) / n;',
  322. ' var variance = 0;',
  323. ' for (var j = 0; j < n; j++) {',
  324. ' variance += Math.pow(numbers[j] - mean, 2);',
  325. ' }',
  326. ' variance = variance / n;',
  327. ' return Math.sqrt(variance);',
  328. '}']);
  329. list = Blockly.JavaScript.valueToCode(block, 'LIST',
  330. Blockly.JavaScript.ORDER_NONE) || '[]';
  331. code = functionName + '(' + list + ')';
  332. break;
  333. case 'RANDOM':
  334. var functionName = Blockly.JavaScript.provideFunction_(
  335. 'math_random_list',
  336. [ 'function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ +
  337. '(list) {',
  338. ' var x = Math.floor(Math.random() * list.length);',
  339. ' return list[x];',
  340. '}']);
  341. list = Blockly.JavaScript.valueToCode(block, 'LIST',
  342. Blockly.JavaScript.ORDER_NONE) || '[]';
  343. code = functionName + '(' + list + ')';
  344. break;
  345. default:
  346. throw 'Unknown operator: ' + func;
  347. }
  348. return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
  349. };
  350. Blockly.JavaScript['math_modulo'] = function(block) {
  351. // Remainder computation.
  352. var argument0 = Blockly.JavaScript.valueToCode(block, 'DIVIDEND',
  353. Blockly.JavaScript.ORDER_MODULUS) || '0';
  354. var argument1 = Blockly.JavaScript.valueToCode(block, 'DIVISOR',
  355. Blockly.JavaScript.ORDER_MODULUS) || '0';
  356. var code = argument0 + ' % ' + argument1;
  357. return [code, Blockly.JavaScript.ORDER_MODULUS];
  358. };
  359. Blockly.JavaScript['math_constrain'] = function(block) {
  360. // Constrain a number between two limits.
  361. var argument0 = Blockly.JavaScript.valueToCode(block, 'VALUE',
  362. Blockly.JavaScript.ORDER_COMMA) || '0';
  363. var argument1 = Blockly.JavaScript.valueToCode(block, 'LOW',
  364. Blockly.JavaScript.ORDER_COMMA) || '0';
  365. var argument2 = Blockly.JavaScript.valueToCode(block, 'HIGH',
  366. Blockly.JavaScript.ORDER_COMMA) || 'Infinity';
  367. var code = 'Math.min(Math.max(' + argument0 + ', ' + argument1 + '), ' +
  368. argument2 + ')';
  369. return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
  370. };
  371. Blockly.JavaScript['math_random_int'] = function(block) {
  372. // Random integer between [X] and [Y].
  373. var argument0 = Blockly.JavaScript.valueToCode(block, 'FROM',
  374. Blockly.JavaScript.ORDER_COMMA) || '0';
  375. var argument1 = Blockly.JavaScript.valueToCode(block, 'TO',
  376. Blockly.JavaScript.ORDER_COMMA) || '0';
  377. var functionName = Blockly.JavaScript.provideFunction_(
  378. 'math_random_int',
  379. [ 'function ' + Blockly.JavaScript.FUNCTION_NAME_PLACEHOLDER_ +
  380. '(a, b) {',
  381. ' if (a > b) {',
  382. ' // Swap a and b to ensure a is smaller.',
  383. ' var c = a;',
  384. ' a = b;',
  385. ' b = c;',
  386. ' }',
  387. ' return Math.floor(Math.random() * (b - a + 1) + a);',
  388. '}']);
  389. var code = functionName + '(' + argument0 + ', ' + argument1 + ')';
  390. return [code, Blockly.JavaScript.ORDER_FUNCTION_CALL];
  391. };
  392. Blockly.JavaScript['math_random_float'] = function(block) {
  393. // Random fraction between 0 and 1.
  394. return ['Math.random()', Blockly.JavaScript.ORDER_FUNCTION_CALL];
  395. };