Skip to content

Fractions #50

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

Open
wants to merge 62 commits into
base: master
Choose a base branch
from
Open

Fractions #50

wants to merge 62 commits into from

Conversation

aliang8
Copy link
Contributor

@aliang8 aliang8 commented Jun 15, 2017

Fixed COMMON_DENOMINATORS to work with non integer denominators.

lib/rule-list.js Outdated
let sameDenom
// rule cannot apply if all of the terms are fractions
// and they all have the same denominator
if (terms.every(term => query.isDiv(term))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid requiring every single term b/c it prevents handling of common denominators of fractions surrounded by non-fraction terms, e.g.

1 + 2/x + 3/y + 4

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should be able to run matchNode on the current node with the #a_0 / #b_0 + ... as the pattern to get the start/end indexes of the first group of 2 or more consecutive fractions. You'll have the pattern to a matching function before you can use it with matchNode.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, I didn't consider using matchNode inside of a defineRule function. Wouldn't that pattern not take into consideration the case of non-fraction terms (1 + 2/x + 3/y + 4)?

lib/rule-list.js Outdated
(num, i) => build.mul(num,
build.number(LCM / denoms[i])))
} else {
newDenoms = build.mul(...denoms)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't ideal, if all of the denominators are x then we'll make this overly complicated.

Copy link
Contributor Author

@aliang8 aliang8 Jun 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When will all the denominators be x? I don't think COMMON_DENOMINATORS will match that.

lib/rule-list.js Outdated
(node) => {
const terms = node.args
// an array storing the index where fraction is negative
const negatives = terms.map(term => query.isNeg(term))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be could if we could extract this logic for reuse. Don't worry about it for this review, but something to think about.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What logic are you referring to? The negatives array logic or the rule itself?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Computing the negatives array from the terms.

Copy link
Contributor Author

@aliang8 aliang8 Jun 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!

@@ -181,12 +188,14 @@ describe('rules', () => {

suite('common denominators', rules.COMMON_DENOMINATOR, [
['2/6 + 1/4', '(2 * 2) / (6 * 2) + (1 * 3) / (4 * 3)'],
['2/(3 + x) + 3/(2 + x)', '(2 * (2 + x)) / ((3 + x) * (2 + x)) + (3 * (3 + x)) / ((3 + x) * (2 + x))'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be some tests with:

  • a mix of numeric denominators and variable denominators
  • repeated variable denominators
  • repeated numeric denominators
  • non-fractional terms in the sum

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

COMMON_DENOMINATORS won't match when the denominator is the same throughout regardless of whether they are variable or numeric. I'm not entirely sure how we want to handle non-fractional terms in the sum yet.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a couple of options:

  • don't include non-fractions when matching
  • include non-fractions, but convert them to fractions before computing the common denominator.

You should ask Gen for their input on this.

What happens if some of the fractions have the same denominator while others don't?

Copy link
Contributor Author

@aliang8 aliang8 Jun 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup good points. I had a test case that was commented out addressing this issue. I will ask for Gen's input.

What happens if some of the fractions have the same denominator while others don't?

It works fine in this scenario. I've added a test case for this. The only time when this rule doesn't match is when all the terms are fractions and they all have the same denominator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a fix for it.

lib/rule-list.js Outdated
? build.div(term, build.number(1))
: query.isNeg(term)
? term.args[0]
: term)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is super confusing. Can you rewrite this use if-else statements?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah xD I realized. Sure.

lib/rule-list.js Outdated

let sameDenom
// rule cannot apply if all of the terms are fractions
// and they all have the same denominator
Copy link
Member

@kevinbarabash kevinbarabash Jun 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth moving this comment up to the top of the function so that people don't have to read the whole function to understand the restrictions.

lib/rule-list.js Outdated
const denom = getDenominator(terms[0])

sameDenom = terms.every(term =>
print(getDenominator(term)) == print(denom))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to encapsulate this into an equals function in utils.js.

lib/rule-list.js Outdated
(node) => {
node = canApplyRule(CONVERT_TO_FRACTION, node)
? applyRule(CONVERT_TO_FRACTION, node)
: node
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We convert non-fraction terms to fractions here, but in the match function part of this rule we return false if any of the terms are non-fractions. Am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first part is correct. The match function of COMMON_DENOMINATORS returns false when all the terms are fractions and they all have the same denoms or none of the terms are fractions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, it has to have at least one fraction... I obviously need to get more sleep. :P

? node.args[0].args[1]
: node.args[1]

export const isDecimal = (node) => query.isNumber(node) && query.getValue(node) % 1 != 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a TODO to move these helper functions to math-nodes?

lib/rule-list.js Outdated
: LCM / denom == 1
? build.number(denom)
: build.mul(build.number(denom),
build.number(LCM / denom)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These chained ternaries are really hard to understand. Can you rewrite this using if-else statements?

lib/rule-list.js Outdated
: build.mul(num,
build.number(LCM / denoms[i])))
} else {
newDenoms = build.mul(...denoms)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens for 2 / x + 3 / x + 4 / y? Can you add a tests case like this? I would expect the result to be 2y / xy + 3y / xy + 4x / xy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good catch. Will fix.

lib/rule-list.js Outdated
} else {
newDenoms = build.mul(...denoms)
newNumerators = nums.map(
(num, i) => build.mul(num, ...denoms.filter(e => e != denoms[i])))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about situations where there are multiple factors in the denominator to begin with? I think there's an implicit assumption that each fraction as a single factor denominator. Can you add a test case for 2 / (x * x) + 3 / (x * y) + 4 / (y * y)?

Copy link
Contributor Author

@aliang8 aliang8 Jun 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. In ChangeTypes, MULTIPLYING_POLYNOMIALScomes before COMMON_DENOMINATORS, so we won't see this exact case. We would see, however, 2/ x^2 + 3/ x^1y^1 + 4 / y^2 which we would want to simplify to (2 * y)/ x^2y^1 + (3 * x) / x^2y^1 + (4 * x) / x^2y^1.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A similar test case that we should also consider is:
2 / ((x^2 + 1)^2 * (x + 1)^1) + 3 / ((x^2 + 1)^2 * x)

@kevinbarabash
Copy link
Member

This looks good except for the failing test. Any idea what's going on there? It looks like the rule returns an empty string.

@aliang8
Copy link
Contributor Author

aliang8 commented Jun 20, 2017

This PR is not ready to merge. I am still working on taking care of more edge cases.

@kevinbarabash
Copy link
Member

@aliang8 is this ready for review now?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants