Calculator: Various Fixes (#4349)

* Fix weird error message.

* Stop algebra overtriggering.

* Fix overflow error.

* Fixed tests.

* Conclude big patch.

* Patch review bugs.
master
PJ Hampton 2017-07-13 23:04:32 +01:00 committed by Zaahir Moolla
parent 588c354075
commit 9c581d9564
4 changed files with 112 additions and 57 deletions

View File

@ -14,13 +14,14 @@ my $calc_regex = qr/^(free)?\s?(online)?\s?calc(ulator)?\s?(online)?\s?(free)?$/
triggers query => $calc_regex;
triggers query => qr'^
(?: [0-9 () τ π e x × * + \- ÷ / \^ \$ £ \. \, _ ! = % ]+ |
(?: [0-9 () τ π e x × * + \- ÷ / \^ \$ £ \. \, _ ! = % °]+ |
\d+\%=?$ |
what\sis| calculat(e|or) | solve | math | log\sof | fact(?:orial?)?(\s+of)? |
times | mult | multiply | divided\sby | plus | minus | cos | tau |
sin | tan | cotan | log | ln | exp | tanh |
sin | sinh | tan | log | ln | exp | tanh | arctan | atan | arccos | acos | asin | arcsin | cosh |
deg(?:rees?)? | rad(?:ians?)? |
sec | csc | squared | sqrt | \d+\s?mod(?:ulo)?\s?\d+ | gross | dozen | pi |
squared | sqrt | cbrt | \d+\s?mod(?:ulo)?\s?\d+ | dozen | pi |
(?:cub(?:ed?|ic)|sq(?:uare)?)\s?r(?:oo)?t(?:\sof)? | cubed? |
score){2,}$
'xi;
@ -28,7 +29,6 @@ my $number_re = number_style_regex();
my %named_operations = (
'\^' => '**',
'x' => '*',
'×' => '*',
'∙' => '*',
'⋅' => '*', # Can be mistaken for dot operator
@ -46,7 +46,6 @@ my %named_operations = (
my %named_constants = (
dozen => 12,
gross => 144,
score => 20,
);
@ -91,9 +90,9 @@ sub spacing {
my ($text, $space_for_parse) = @_;
$text =~ s/\s{2,}/ /g;
$text =~ s/(\s*(?<!<)(?:[\+\^xX×∙⋅\*\/÷]|(?<!\de)\-|times|plus|minus|divided\s*by)+\s*)/ $1 /ig;
$text =~ s/(\s*(?<!<)(?:[\+\^∙⋅\*\/÷]|(?<!\de)\-|times|plus|minus|divided\s*by)+\s*)/ $1 /ig;
$text =~ s/\s*dividedby\s*/ divided by /ig;
$text =~ s/(\d+?)((?:dozen|pi|gross|squared|score))/$1 $2/ig;
$text =~ s/(\d+?)((?:dozen|pi|squared|score))/$1 $2/ig;
$text =~ s/([\(\)])/ $1 /g if $space_for_parse;
$text =~ s/\|/,/g;
$text =~ s/\s{2,}/ /g;
@ -110,7 +109,7 @@ sub rewriteQuery {
$text =~ s/minus/-/g;
$text =~ s/times|mult/×/g;
$text =~ s/divided\s?by/÷/g;
$text =~ s/(cos|tau|τ|sin|tan|cotan|log|ln|exp|tanh|π|sec|csc|squared|sqrt|gross|dozen|pi|e|score)\s*\1/$1/g;
$text =~ s/(a?cos|tau|τ|a?sinh?|a?tan|log|ln|exp|tanh|π|squared|sqrt|dozen|pi|e|score)\s*\1/$1/g;
$text =~ s|([x × % + ÷ / \^ \$ £ \. \, _ =])\s*\1|$1|gx;
return $text;
@ -128,6 +127,18 @@ sub rewriteFunctions {
$query =~ s/fact\(?(\d+)\)?/$1!/;
$query =~ s/fact(?:orial?)?(?:\s+of)?\s+?(\(?\d+\)?)?/$1!/;
$query =~ s/exp\s?\(?(\d+)\)?/exp($1)/i;
$query =~ s/cube\s?\((.+)\)/cube($1)/gi;
$query =~ s/cube\s?(\d+|e|pi|tau)/cube($1)/gi;
$query =~ s/(\d+|e|pi|tau)\scubed/cube($1)/gi;
# Preprocesses cbrt / sqrt
$query =~ s/(?:cu(?:be)?d?|cubic)\s?r(?:oo)?t(?:\sof)?/cbrt/ig;
$query =~ s/sq(?:uare)?\s?r(?:oo)?t(?:\sof)?/sqrt/ig;
$query =~ s/(cbrt|sqrt)\s?\((.+)\)/$1($2)/i;
$query =~ s/(cbrt|sqrt)\s?(\d+|e|pi|tau)/$1($2)/i;
# Preprocesses Log/Ln
$query =~ s/log\sof\s(\d+)/log($1)/i;
$query =~ s/log10\((\d+)\)/log($1)/i;
@ -138,10 +149,13 @@ sub rewriteFunctions {
$query =~ s/ln\s?(\d+)/ln($1)/i;
# Preprocesses Trig functions
$query =~ s/arctan/atan/ig;
$query =~ s/arccos/acos/ig;
$query =~ s/arcsin/asin/ig;
$query =~ s/(deg)rees?/$1/ig;
$query =~ s/rad(?:ians?)?//ig;
$query =~ s/(sin|cos|tan)\s?(\d+(?: deg)?)/$1($2)/ig;
$query =~ s/(a?sinh?|a?cosh?|a?tanh?|tan)\s?(\d+(?: deg)?)/$1($2)/ig;
return $query;
}
@ -163,9 +177,10 @@ handle query => sub {
};
}
# Help support the query if there is degrees involed
$query =~ s/°/ deg/gi;
return if ( m/deg(rees?)?|°/i && m/rad(ians?)?/); # we don't support a mix of degrees and radians in the same query
$query = rewriteFunctions($query);
# throw out obvious non-calculations immediately
return if $query =~ qr/(\$(.+)?(?=£|€))|(£(.+)?(?=\$|€))|(€(.+)?(?=\$|£))/; # only let one currency type through
return if $req->query_lc =~ /^0x/i; # hex maybe?
@ -175,6 +190,8 @@ handle query => sub {
return if $query =~ m/^(\+?\d{1,2}(\s|-)?|\(\d{2})?\(?\d{3,4}\)?(\s|-)?\d{3}(\s|-)?\d{3,4}(\s?x\d+)?$/; # Probably are searching for a phone number, not making a calculation
return if $query =~ m/(\d+)\s+(\d+)/; # if spaces between numbers then bail
return if $query =~ m/^\)|\($/; # shouldn't open with a closing brace or finish with an opening brace
return if $query =~ m/(a?cosh?|tau|a?sin|a?tan|log|ln|exp|tanh|cbrt|cubed?)e?$/i; # stops empty functions at end or with <func>e
return if $query =~ m#(?:x(\^|/)|(\^|/)x)#; # stops triggering on what is most likely algebra
# some shallow preprocessing of the query
$query =~ s/^(?:what is|calculat(e|or)|solve|math)//i;
@ -187,11 +204,14 @@ handle query => sub {
return if $query =~ m{[x × ∙ ⋅ * + \- ÷ / \^ \$ £ € \. ,]{3,}}ix;
return if $query =~ m/\$[^\d\.]/;
return if $query =~ m/\(\)/;
return if $query =~ m/^e\d+/;
return if $query =~ m#(\+|-|\*|/|x|\^)\)#xi; # can't have an operator before a closing paren
return if $query =~ m{//};
return if $query =~ m/(^|[^\d])!/g;
return if $query =~ m/0x[A-Za-z]{2,}/;
return if $query =~ m/X\d+/;
return if $query =~ m/9\/11/; # date edge case
return if $query =~ m/\d+e\+\d+/;
return if $query =~ m{(?:7|9)/11}; # date edge case, US supermarket
return if $query =~ m/.+=.+/; # check there isn't something on both sides of the equals sign
return if $query =~ /^(?:minus|-|\+)\d+$/;
@ -202,8 +222,8 @@ handle query => sub {
while (my ($name, $operation) = each %named_operations) {
$query =~ s#$name#$operation#xig; # We want these ones to show later.
}
return if ($tmp_expr eq $query) && ($query !~ /\de|fact|cos|tan|sin|log|ln|mod|!/i); # If it didn't get spaced out, there are no operations to be done.
$query =~ s/(?<!e)x(?!p)/\*/ig; # stops the x in exp being converted to *
return if ($tmp_expr eq $query) && ($query !~ /\de|fact|a?cos|a?tan|sin|cbrt|cubed?|log|ln|exp|mod|!/i); # If it didn't get spaced out, there are no operations to be done.
# Now sub in constants
while (my ($name, $constant) = each %named_constants) {
@ -214,7 +234,7 @@ handle query => sub {
return unless $style;
my $spaced_query = prepare_for_frontend($query, $style);
return if scalar(split(" ", $spaced_query)) < 2 && ($spaced_query !~ /^(fact|csc|sec|sqrt|cos|tan|sin|log|ln|mod|modulo|\d+\!$)/i);
return if scalar(split(" ", $spaced_query)) < 2 && ($spaced_query !~ /^(fact|cubed?|cbrt|sqrt|exp|a?cosh?|a?tan|a?sin|log|ln|mod|modulo|\d+\!$)/i);
return '', structured_answer => {
data => {

View File

@ -105,12 +105,12 @@ DDH.calculator = DDH.calculator || {};
.replace(/<sup>2<\/sup>/g, '^2')
.replace(/<sup>3<\/sup>/g, '^3')
.replace(/<sup>(((-?(\d*.)?(\d+))|([πe(log|ln\(\d+\))]))+)<\/sup>/g, RewriteExpression.exponent)
.replace(/(EE) (\d+(\.\d{1,})?)/g, RewriteExpression.ee)
.replace(/(EE) ([^)]+)/g, RewriteExpression.ee)
// 5. handles scientific calculation functions
.replace(/log(?:\(([^),]+)\)|\s(\d+))/g, RewriteExpression.log10)
.replace(/ln\(?([^)]+)\)?/g, RewriteExpression.log)
.replace(/(sin|cos|tan)\(?([^)]+)\)?/g, RewriteExpression.trig)
.replace(/(a?sinh?|a?cosh?|a?tanh?)\(?([^)]+)\)?/g, RewriteExpression.trig)
.replace(/(\d+)\s?mod(?:ulo)?\s?(\d+)?/g, 'mod($1,$2)')
// 6. handles constants
@ -458,15 +458,17 @@ DDH.calculator = DDH.calculator || {};
try {
var total = math.eval(
normalizedExpression
).toString()
).toFixed(11)
total = parseFloat(total).toString();
var tree = math.parse(normalizedExpression);
var parsed_expression = tree.toString({parenthesis: 'all'});
// remove rounding from expression
parsed_expression = parsed_expression.replace(/(round\((.+)\), 11)/, '$2');
} catch(err) {
if(!expressionFromSearchBar) {
display.value = "Error";
ExpressionParser.setExpression();
setCButtonState("C");
return false;
} else {
display.value = "";
evaluated = true;
@ -476,9 +478,10 @@ DDH.calculator = DDH.calculator || {};
'calculator', {
q: DDG.get_query_encoded()
});
return false;
}
return false;
}
ExpressionParser.setExpression(parsed_expression);
evaluated = true;
setCButtonState("C");
@ -497,6 +500,7 @@ DDH.calculator = DDH.calculator || {};
}
/**
* Clear
*
@ -735,6 +739,10 @@ DDH.calculator = DDH.calculator || {};
var expression = display.value.split(" ");
if(expression[expression.length-1].indexOf(".") > -1) { return false; }
}
// if it's an operator, we'll leave the yRootState
if(yRootState === true && Utils.isOperand(element)) {
yRootState = false;
}
// if element is math function or square root, increment paren total
if(Utils.isMathFunction(element) || element === "√(") {
@ -994,6 +1002,7 @@ DDH.calculator = DDH.calculator || {};
calculator(evt);
setFocus();
e.stopImmediatePropagation();
e.preventDefault();
});
$calcInputTrap.keydown(function(e){

View File

@ -17,7 +17,7 @@
<div class="tile__tabs">
<!-- scientific calculations -->
<!-- scientific keyboard -->
<div class="tile__calc__col tile__tab__sci">
<span class="tile__ctrl__toggle">
<span>RAD</span>
@ -27,54 +27,54 @@
</label>
<span>DEG</span>
</span>
<button class="tile__ctrl__btn" data-cmd="META_PAR_OPEN" value="(">(</button>
<button class="tile__ctrl__btn" data-cmd="META_PAR_CLOSE" value=")">)</button>
<button class="tile__ctrl__btn" value="(">(</button>
<button class="tile__ctrl__btn" value=")">)</button>
<button class="tile__ctrl__btn" data-cmd="FN_SIN" value="sin(">sin</button>
<button class="tile__ctrl__btn" data-cmd="FN_COS" value="cos(">cos</button>
<button class="tile__ctrl__btn" data-cmd="FN_TAN" value="tan(">tan</button>
<button class="tile__ctrl__btn" data-cmd="CONST_PI" value="π">&#x3c0;</button>
<button class="tile__ctrl__btn" value="sin(">sin</button>
<button class="tile__ctrl__btn" value="cos(">cos</button>
<button class="tile__ctrl__btn" value="tan(">tan</button>
<button class="tile__ctrl__btn" value="π">&#x3c0;</button>
<button class="tile__ctrl__btn" data-cmd="FN_FACT" value="!">x!</button>
<button class="tile__ctrl__btn" data-cmd="FN_POW_2" value="<sup>2</sup>">x<sup>2</sup></button>
<button class="tile__ctrl__btn" data-cmd="FN_POW_3" value="<sup>3</sup>">x<sup>3</sup></button>
<button class="tile__ctrl__btn" data-cmd="FN_POW_N" value="<sup>□</sup>">x<sup>y</sup></button>
<button class="tile__ctrl__btn" value="!">x!</button>
<button class="tile__ctrl__btn" value="<sup>2</sup>">x<sup>2</sup></button>
<button class="tile__ctrl__btn" value="<sup>3</sup>">x<sup>3</sup></button>
<button class="tile__ctrl__btn" value="<sup>□</sup>">x<sup>y</sup></button>
<button class="tile__ctrl__btn" data-cmd="FN_DIV_1" value="1/(">1/x</button>
<button class="tile__ctrl__btn" data-cmd="FN_SQRT" value="&#x221A;(">&#x221A;x</button>
<button class="tile__ctrl__btn" data-cmd="FN_SQRT_N" value="<sup>□</sup>&#x221A"><sup>x</sup>&#x221A;y</button>
<button class="tile__ctrl__btn" data-cmd="FN_EE" value="EE">EE</button>
<button class="tile__ctrl__btn" value="1/(">1/x</button>
<button class="tile__ctrl__btn" value="&#x221A;(">&#x221A;x</button>
<button class="tile__ctrl__btn" value="<sup>□</sup>&#x221A"><sup>x</sup>&#x221A;y</button>
<button class="tile__ctrl__btn" value="EE">EE</button>
<button class="tile__ctrl__btn" data-cmd="FN_LOG" value="log(">log</button>
<button class="tile__ctrl__btn" data-cmd="FN_LN" value="ln(">ln</button>
<button class="tile__ctrl__btn" data-cmd="FN_POW_E" value="e<sup>□</sup>">e<sup>x</sup></button>
<button class="tile__ctrl__btn" data-cmd="CONST_E" value="e">e</button>
<button class="tile__ctrl__btn" value="log(">log</button>
<button class="tile__ctrl__btn" value="ln(">ln</button>
<button class="tile__ctrl__btn" value="e<sup>□</sup>">e<sup>x</sup></button>
<button class="tile__ctrl__btn" value="e">e</button>
</div>
<!-- basic calculations -->
<!-- numeric keyboard -->
<div class="tile__calc__col tile__tab__basic">
<button class="tile__ctrl__btn tile__ctrl--double" id="clear_button" data-cmd="META_CLEAR" value="C">C</button>
<button class="tile__ctrl__btn tile__ctrl--ops" data-cmd="OP_PCT" value="%">%</button>
<button class="tile__ctrl__btn tile__ctrl--important" data-cmd="OP_DIV" value="÷">&divide;</button>
<button class="tile__ctrl__btn tile__ctrl--double" id="clear_button" value="C">C</button>
<button class="tile__ctrl__btn tile__ctrl--ops" value="%">%</button>
<button class="tile__ctrl__btn tile__ctrl--important" value="÷">&divide;</button>
<button class="tile__ctrl__btn" value="7">7</button>
<button class="tile__ctrl__btn" value="8">8</button>
<button class="tile__ctrl__btn" value="9">9</button>
<button class="tile__ctrl__btn tile__ctrl--important" data-cmd="OP_MULT" value="×">&times;</button>
<button class="tile__ctrl__btn tile__ctrl--important" value="×">&times;</button>
<button class="tile__ctrl__btn" value="4">4</button>
<button class="tile__ctrl__btn" value="5">5</button>
<button class="tile__ctrl__btn" value="6">6</button>
<button class="tile__ctrl__btn tile__ctrl--important" data-cmd="OP_MINUS" value="-">&minus;</button>
<button class="tile__ctrl__btn tile__ctrl--important" value="-">&minus;</button>
<button class="tile__ctrl__btn" value="1">1</button>
<button class="tile__ctrl__btn" value="2">2</button>
<button class="tile__ctrl__btn" value="3">3</button>
<button class="tile__ctrl__btn tile__ctrl--important" data-cmd="OP_PLUS" value="+">+</button>
<button class="tile__ctrl__btn tile__ctrl--important" value="+">+</button>
<button class="tile__ctrl__btn tile__ctrl--double" value="0">0</button>
<button class="tile__ctrl__btn" value=".">.</button>
<button class="tile__ctrl__btn tile__ctrl--important" data-cmd="META_PROCEED" value="=">=</button>
<button class="tile__ctrl__btn tile__ctrl--important" value="=">=</button>
</div>
<!-- the ledger (history) section of the calculators UI -->
@ -82,4 +82,4 @@
</div>
</div>
</div>
</div>

View File

@ -90,12 +90,6 @@ ddg_goodie_test(
'sin(1)' => build_test(
'sin(1)'
),
'csc(1)' => build_test(
'csc(1)'
),
'sec(1)' => build_test(
'sec(1)'
),
'$3.43+$34.45' => build_test(
'$3.43 + $34.45'
),
@ -147,9 +141,6 @@ ddg_goodie_test(
'2,90 + 4,6' => build_test(
'2.90 + 4.6'
),
'2,90 + sec(4,6)' => build_test(
'2.90 + sec(4.6)'
),
'100 - 96.54' => build_test(
'100 - 96.54'
),
@ -422,8 +413,43 @@ ddg_goodie_test(
'cos(103*232+22)+2' => build_test(
'cos(103 * 232 + 22) + 2'
),
'cos(103*232+22)+2' => build_test(
'cos(103 * 232 + 22) + 2'
),
'square root of 25' => build_test(
'sqrt(25)'
),
'77 * square root 25 + 2' => build_test(
'77 * sqrt(25) + 2'
),
'77 * cube rt 25 + 2' => build_test(
'77 * cbrt(25) + 2'
),
'cubic rt of 90' => build_test(
'cbrt(90)'
),
'99 cubed' => build_test(
'cube(99)'
),
'2 + cube 66 + 2' => build_test(
'2 + cube(66) + 2'
),
'e2e4' => undef,
'cosh(4+-)' => undef,
'232 * 2 cube' => undef, # /cube/ can't be at end, only /cubed/
'sine' => undef,
'loge' => undef,
'lne' => undef, # a search with high click through
'cose' => undef,
'tane' => undef,
'1e+6' => undef,
'7/11' => undef, # an ambiguity. Probably looking for US store
'sec^2-1' => undef,
'(x^2-1)*(5x^4+2x+1)' => undef,
'88y * 1312' => undef,
'23x+3x' => undef,
'3a / 8b' => undef,
'tan of 88 degrees and radians' => undef,
'sin 88 degrees + sin 10 radians' => undef,
'1432 / 28 2' => undef,