New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

eval inequations with string scope #680

Closed
boeni opened this Issue Jul 4, 2016 · 17 comments

Comments

Projects
None yet
6 participants
@boeni

boeni commented Jul 4, 2016

I'm using eval() in an nodejs project and it works great until i found this:

    print(math.eval('W<=5*Z', {W:'50',Z:'300'}));    // false
    print(math.eval('W<=5*Z', {W:50,Z:300}));        // true

Shouldn't the string scope eval the same way the number scope does?

PS: I tried the code above also on
http://jsbin.com/devacu/edit

@josdejong

This comment has been minimized.

Owner

josdejong commented Jul 4, 2016

Strings are compared alphabetically.

@boeni

This comment has been minimized.

boeni commented Jul 5, 2016

I see.
If this is the desired behavior then i think you can close this issue, but maybe as a user I would expect math.eval to behave in a more 'mathematical way' than the 'programming way'.
I mean the current behavior is pretty much the same as

print('50'<='1500'); // false

which is standard javascript with substituted strings?

@josdejong

This comment has been minimized.

Owner

josdejong commented Jul 5, 2016

Yes, makes sense to change this behavior to converting strings into numbers before comparing them. We have the same behavior for most operators (like +, -, *, etc).

Let's give this idea a bit more time and if we can't come up with good reasons to keep the current behavior (comparing strings alphabetically), let's change this in the first next breaking release.

@josdejong josdejong referenced this issue Jul 8, 2016

Closed

Breaking changes for v4 #682

13 of 13 tasks complete
@ThomasBrierley

This comment has been minimized.

Contributor

ThomasBrierley commented Aug 13, 2016

This tripped me up too, even though it's also JavaScript's default behaviour, I think this would be a good default behaviour unless there is a use case within mathjs for string comparison.

In my case I was intentionally adding variables as string onto a scope because I wanted to retain properties of the user input that are lost in a string -> number conversion for the purpose of user input validation.

@josdejong

This comment has been minimized.

Owner

josdejong commented Aug 14, 2016

I remember we've discussed this long time ago, but maybe we could think about doing the same with preprocessing numbers into BigNumbers or Fractions. For example here:

math.config({number: 'BigNumber'})
math.add(0.1, 0.2)

You may expect the library to convert the numbers to BigNumbers and then add them up, which is not the case. Having option predictable: false, we could convert numbers into BigNumbers. This may not be easy to implement, but could be interesting to look into.

@ThomasBrierley

This comment has been minimized.

Contributor

ThomasBrierley commented Aug 15, 2016

I just found a use case for string comparison in my current project, although It's not reasonable to say it's within the scope of mathjs. However it highlighted to me that some type coercion is already being applied, maybe honing it's behaviour could solve this without sacrificing string operations.

Coercion or Error happens when types do not match, also the behaviour of when and how is quite different to JavaScript. Using the string truths "9" > "10" and "10" < "9" I can illustrate which way things are being converted in different scenarios:

math.eval(' "9" > x ', {x: 10});
// true (right symbol -> string)

math.eval(' 9 > x ', {x: "10"});
// Error (didn't try to coerce)

math.eval(' x < "9" ', {x: 10});
// false (right literal -> number)

math.eval(' x < 9 ', {x: "10"});
// Error (didn't try to coerce)

(excluded mixed type literals which all throw errors)

There seems to be a difference between how symbols and literals are coerced and on what side of the operator they are. By comparison JavaScript doesn't appear to apply different rules between symbols and literals or which way around they are. It simply has a type preference depending on the operator.

You may expect the library to convert the numbers to BigNumbers and then add them up, which is not the case.

I'm not sure I understand the implication of this. Would the result of the operation become a BigNumber? And would there be a difference if the inputs were converted BigNumbers first? or does it depend on the operation?

@ThomasBrierley

This comment has been minimized.

Contributor

ThomasBrierley commented Aug 15, 2016

Just to clarify, my point was that @boeni's original issue would not be present if Mathjs coerced values with the > < * operands like JavaScript, which all have a preference or requirement for numbers:

"9" > 10
// false (left literal -> number)

9 > "10"
// false (right literal -> number)

"9" > "10" * "2"
// false (all coerced to numbers)

  > (prefers number, input: string, number)
 / \
9   * (requires number, input: string, string)
   / \
 10   2
@josdejong

This comment has been minimized.

Owner

josdejong commented Aug 15, 2016

Thanks, you are right, math.js behaves inconsistent with type coercion. That's not a good thing and should be be fixed and made consistent independent of the outcome of this discussion.

What I meant here is that currently the option {number: 'BigNumber'} only applies to the expression parser, whilst you might want this option to automatically apply to any calculation: first convert numeric inputs to BigNumber, then evaluate, so indeed in that case math.add(0.1, 0.2) would output a BigNumber 0.3.

@ThomasBrierley

This comment has been minimized.

Contributor

ThomasBrierley commented Aug 16, 2016

Ah yes I see now, I had made that assumption previously when trying to use Fractions globally and noticed the difference. I think that option could be useful.

@balagge

This comment has been minimized.

balagge commented Jun 23, 2017

Type coercion maybe comes from javascript, but that is a completely different environment. And, frankly, even in javascript it causes a lot of trouble.

For example, in javascript, "2"*"3" returns 6 (a number) but "2"+"3" returns "23" (a string).
Pretty unintuitive, huh?

By the way, the post that led me here, #881 has an example which works in mathjs exactly as in javascript:

"5" <= "9" // true
"5" <= "10" // false

because there is no type coercion here, the strings are compared lexicographically.

Any behind-the-scene type conversion leads to unwanted surprises, in my opinion. This is because heuristic conversion will result in loss of information, and it is just a question of time that we will find two different cases where the loss of information produces the same result but we would want to have different results...

I guess the real question here is "what does a string mean in mathjs?", or "why would someone want to use string values in a mathematical environment?".

More direct question is "what are the intended operations on a string value?"

@balagge

This comment has been minimized.

balagge commented Jun 23, 2017

In order to clarify: I believe that no type coercion should ever be done, because it leads to unwanted surprises.

@balagge

This comment has been minimized.

balagge commented Jun 23, 2017

The crucial question is (see above), whether or not mathjs wants to manipulate string objects at all.

So, ask (and answer) the question: what does "abc" (a string) mean in a mathematical environment? Do we want to manipulate it, have operations / functions that treat it as a string? Or is it meaningless?

This is a particularly difficult question, because strings have very weak semantics generally (and no well-defined meaning in mathematics). But if we want to have some operations that treat strings "as strings", then probably the following operations ("algebras") qualify:

  • concatenation of two strings (best notation would be multiplication, representing multiplication of a semigroup, because that is how strings actually behave: a free semigroup with as many generator elements as the characters available)
  • comparison - the only reasonable comparison ever invented for strings is the lexicographical order
  • maybe some form of substring operations (or functions)
  • anything else?

Not much

Now if we answer "Yes" to the ultimate question above (i.e. we DO want to have strings and string operations in a math environment) I think all implicit conversions should be removed, because those are very deceptive.

On the other hand, if we do NOT need strings at all, then maybe we could convert all strings to numbers, but that would mean converting "abc" to NaN, probably.

These are the only consistent aproaches, I beleive. Any other attempt to use heuristics, conversions, etc. (like javascript does) will lead to an infinite series of questions of the form "if I enter X then I get ..., so how is it that if I enter Y then I get ...".

@balagge

This comment has been minimized.

balagge commented Jun 23, 2017

BTW type coercion, as in javascript, is the single worst idea ever introduced to a programming language.

I just found this with google, try it, it is fun:

alert((![]+[])[+[]]+(![]+[])[+!+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]);

Then try to figure out what is going on...

@ThomasBrierley

This comment has been minimized.

Contributor

ThomasBrierley commented Jun 23, 2017

In order to clarify: I believe that no type coercion should ever be done, because it leads to unwanted surprises.

Because type coercion will sometimes just "happen" in an operator's implementation, this basically means adding type checking to all operator interfaces. I don't have much of an opinion on whether or not it's a good thing to make MathJS strict, however I suspect it will be quite a bit of work. For instance, look at + (add):

Add has valid operations for scalars, vectors, matrices and combinations of those like pointwise scalar vector addition. Each possible combination would have to be considered and type checked... and that's just for add!

So, ask (and answer) the question: what does "abc" (a string) mean in a mathematical environment? Do we want to manipulate it, have operations / functions that treat it as a string? Or is it meaningless?
This is a particularly difficult question, because strings have very weak semantics generally (and no well-defined meaning in mathematics)

I think it's important to remember that MathJS is an expression parser, not a mathematical notation parser... Strings have legitimate uses in expressions in a programmatic sense, for instance "objects" are a thing in MathJS, and without strings, you could not use objects as lookup tables.

Mathematical notation on the other hand does not need strings because it is informative, not functional.

@josdejong

This comment has been minimized.

Owner

josdejong commented Jun 23, 2017

Thanks @balagge .

Type coersion can be very tricky an the way it's implemented in JavaScript has quite some pitfalls. It can also be incredible powerful when implemented right, like in Matlab it works like a charm and I love to be able to just add a number to a complex number. I'm also sooo glad that I don't have to explicitly convert int's to long's and floats to doubles all the time when working in Java, else we would end up with a conversion hell I'm afraid. It's one of the corner stones of math.js too enabling to use different data types mixed together.

We should be careful with implicit conversion though, and want to make some changes in math.js in this regard. Like not converting null to 0 anymore.

As for coersion of strings: I think the right thing for math.js is to think mathematical, and implicitly convert strings into numbers (#680, #881). A pragmatic reason for this is that math.js is often used in the browser, where calculations are done with input coming from UI form inputs which always contains with strings. Automatically converting this input to a numeric value would be convenient.

@psyCodelist

This comment has been minimized.

psyCodelist commented Jul 24, 2018

Is there a way to make engine not to convert strings to numbers? I think it limits the usage of the library. how can I define point = ( id == 'abcd') ? 40 : 30

In my case I'm passing id as scope and it is string...

Thank you,

@harrysarson

This comment has been minimized.

Collaborator

harrysarson commented Jul 25, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment