Numbers#

Beancount supports arithmetic expressions almost wherever numbers are accepted.

Construction#

NumberExpr supports construction with from_value:

expr = models.NumberExpr.from_value(decimal.Decimal(42))
assert isinstance(expr, models.NumberExpr)

Value access#

NumberExpr supports value reading and writing. Note that value writing essentially replaces the whole expression.

expr = p.parse('1+2*3', models.NumberExpr)
_print_model(expr)
print(expr.value)

expr.value = 8
_print_model(expr)
print(expr.value)
1+2*3
7
8
8

Arithmetic#

The following arithmetic operations are supported on NumberExpr:

  • +, -, *, /, //

  • +=, -=, *=, /=, //=

  • +, - (unary)

The operands may be int, decimal.Decimal, or NumberExpr.

expr = p.parse('1*2+3', models.NumberExpr)
expr *= 4
expr += 5
expr = -expr

_print_model(expr)
print(expr.value)
-((1*2+3) * 4 + 5)
-25

Ambiguity#

One confusing issue with beancount arithmetic expressions is about custom where multiple of them can appear consecutively. For example, what does the following custom encode?

2000-01-01 custom "foo" 1 2 -3

Somewhat surprisingly:

$ echo '2000-01-01 custom "foo" 1 2 -3' | bean-report /dev/stdin print
2000-01-01 custom "foo" 1 -1

If we want the arguments to be [1, 2, -3], it must be represented as 2000-01-01 "foo" 1 2 (-3):

$ echo '2000-01-01 custom "foo" 1 2 (-3)' | bean-report /dev/stdin print
2000-01-01 custom "foo" 1 2 -3

autobean-refactor automatically disambiguates custom when constructed with Custom.from_children or Custom.from_value:

custom = models.Custom.from_value(
    date=datetime.date(2000, 1, 1),
    type='foo',
    values=map(decimal.Decimal, [1, 2, -3]))
_print_model(custom)
2000-01-01 custom "foo" 1 2 (-3)

However, that doesn’t cover all the cases, you may still accidentally create ambiguity when adding, removing, or modifying arguments, in which case you may manually disambiguate with NumberExpr.wrap_with_parenthesis.

custom = p.parse('2000-01-01 custom "foo" 1 2', models.Custom)

custom.values[1] = decimal.Decimal('-2')
_print_model(custom)

custom.raw_values[1].wrap_with_parenthesis()
_print_model(custom)
2000-01-01 custom "foo" 1 -2
2000-01-01 custom "foo" 1 (-2)