fractions.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. from itertools import combinations
  2. from .utils import nary_node, least_common_multiple
  3. from ..node import ExpressionLeaf as L, OP_DIV, OP_ADD, OP_MUL, OP_NEG
  4. from ..possibilities import Possibility as P, MESSAGES
  5. from ..translate import _
  6. def match_constant_division(node):
  7. """
  8. a / 0 -> Division by zero
  9. a / 1 -> a
  10. 0 / a -> 0
  11. a / a -> 1
  12. """
  13. assert node.is_op(OP_DIV)
  14. p = []
  15. nominator, denominator = node
  16. # a / 0
  17. if denominator == 0:
  18. raise ZeroDivisionError('Division by zero: %s.' % node)
  19. # a / 1
  20. if denominator == 1:
  21. p.append(P(node, division_by_one, (nominator,)))
  22. # 0 / a
  23. if nominator == 0:
  24. p.append(P(node, division_of_zero))
  25. # a / a
  26. if nominator == denominator:
  27. p.append(P(node, division_by_self))
  28. return p
  29. def division_by_one(root, args):
  30. """
  31. a / 1 -> a
  32. """
  33. return args[0]
  34. def division_of_zero(root, args):
  35. """
  36. 0 / a -> 0
  37. """
  38. return L(0)
  39. def division_by_self(root, args):
  40. """
  41. a / a -> 1
  42. """
  43. return L(1)
  44. def match_add_constant_fractions(node):
  45. """
  46. 1 / 2 + 3 / 4 -> 2 / 4 + 3 / 4 # Equalize denominators
  47. 2 / 4 + 3 / 4 -> 5 / 4 # Equal denominators, so nominators can
  48. # be added
  49. 2 / 2 - 3 / 4 -> 4 / 4 - 3 / 4 # Equalize denominators
  50. 2 / 4 - 3 / 4 -> -1 / 4 # Equal denominators, so nominators can
  51. # be subtracted
  52. """
  53. assert node.is_op(OP_ADD)
  54. p = []
  55. def is_division(node):
  56. return node.is_op(OP_DIV) or \
  57. (node.is_op(OP_NEG) and node[0].is_op(OP_DIV))
  58. fractions = filter(is_division, node.get_scope())
  59. for a, b in combinations(fractions, 2):
  60. na, da = a if a.is_op(OP_DIV) else a[0]
  61. nb, db = b if b.is_op(OP_DIV) else b[0]
  62. if da == db:
  63. # Equal denominators, add nominators to create a single fraction
  64. p.append(P(node, add_nominators, (a, b)))
  65. elif da.is_numeric() and db.is_numeric():
  66. # Denominators are both numeric, rewrite both fractions to the
  67. # least common multiple of their denominators. Later, the
  68. # nominators will be added
  69. denom = least_common_multiple(da.value, db.value)
  70. p.append(P(node, equalize_denominators, (a, b, denom)))
  71. return p
  72. def equalize_denominators(root, args):
  73. """
  74. 1 / 2 + 3 / 4 -> 2 / 4 + 3 / 4
  75. a / 2 + b / 4 -> 2a / 4 + b / 4
  76. """
  77. denom = args[2]
  78. scope = root.get_scope()
  79. for fraction in args[:2]:
  80. n, d = fraction[0] if fraction.is_op(OP_NEG) else fraction
  81. mult = denom / d.value
  82. if mult != 1:
  83. n = L(n.value * mult) if n.is_numeric() else L(mult) * n
  84. if fraction.is_op(OP_NEG):
  85. scope[scope.index(fraction)] = -(n / L(d.value * mult))
  86. else:
  87. scope[scope.index(fraction)] = n / L(d.value * mult)
  88. return nary_node('+', scope)
  89. def add_nominators(root, args):
  90. """
  91. a / b + c / b -> (a + c) / b
  92. a / b + (-c / b) -> (a + (-c)) / b
  93. """
  94. # TODO: is 'add' Appropriate when rewriting to "(a + (-c)) / b"?
  95. ab, cb = args
  96. a, b = ab
  97. if cb[0].is_op(OP_NEG):
  98. c = cb[0][0]
  99. substitution = (a + (-c)) / b
  100. else:
  101. c = cb[0]
  102. substitution = (a + c) / b
  103. scope = root.get_scope()
  104. # Replace the left node with the new expression
  105. scope[scope.index(ab)] = substitution
  106. # Remove the right node
  107. scope.remove(cb)
  108. return nary_node('+', scope)
  109. def match_expand_and_add_fractions(node):
  110. """
  111. a * b / c + d * b / c -> (a + d) * (b / c)
  112. a * b / c + (- d * b / c) -> (a + (-d)) * (b / c)
  113. """
  114. # TODO: is 'add' Appropriate when rewriting to "(a + (-d)) / * (b / c)"?
  115. assert node.is_op(OP_MUL)
  116. p = []
  117. return p