Auto Calculate, Chaining & Defaults

powerOne is a smart system that will auto-calculate rows when enough information is available to do so. For instance, the included Discount template will calculate automatically if two of the three rows are entered. Entering an Original Price of 39.95 and a Sale Price of 29.95 will yield a 25.031% discount. Notice that there is also enough information to calculate Difference.

Internally powerOne tracks every row entered and calculated, and its relationship to other rows. Consider the following, simple example:

      "First Row" "" a ::
      "Second Row" "" b ::
      "Answer" "" c:b+a

The variable 'a' and 'b' are also referenced by variable 'c' in its formula. If we enter a number for variable 'a', powerOne cannot calculate yet because it doesn't have all the information it needs to calculate 'c'. But when 'b' is entered, it now has all the information it needs and can complete the calculation.

Chaining

While most of the time powerOne is smart enough to figure out what to calculate next, sometimes it needs a little help. Chaining gives the developer the ability to tell the engine what comes next in case the engine can't figure it out for itself.

"Label" "Default" variable,precision,chain:equation ::

In essence, chain says calculate this variable when you've finished calculating the current one. It is optional, only one chain variable can be designated per row, and it can be used with any data type. Note that its position is just before the colon and equation in the definition of the row. The chain variable, for instance, can be used for for conversion templates, as this example demonstrates (easier method introduced in v2.0.1: onEnter):

Ounces "" oz,lb:t*2000*16 ::
Pounds "" lb,t:oz/16 ::
Tons "" t,oz:lb/2000

Notice that each row's formula references the previous row's variable (the top row references the bottom row). If Ounces is entered, for example, powerOne will only calculate Tons (because Ounces 'lb' is the known value) because it has enough information about 't' to perform that computation. Telling Tons to calculate 'oz' next ensures that Ounces is calculated and chaining 'lb' from Ounces ensures that Pounds is calculated after that. It quits calculating before recalculating Tons again because all results are known.

Defaults

Another trick to help powerOne is to use default values. Default values tell the engine that this variable is already known. It also means less data entry for common cases. A common example is to set Loan Years in a Mortgage template to 30 since the most common loan scenario in the U.S. is a 30-year mortgage. See the Examples for other uses of default.

Numbers can have an optional default value. Lists always require one. Tables don't have default values and Dates either default to a designated date or to today's date if excluded.

Auto Calculate Logic

powerOne puts all three capabilities together to auto calculate templates:

1. On first load or when New is tapped, rows with a default value are known and rows without a default value are unknown. Lists and dates are always known since they always have a default value.

2. When an unknown value is edited it becomes known and nothing else happens (in this step). When an already known value is edited, it stays known, but also any other rows that depend on the newly edited value (it's used in their formula) are invalidated and marked unknown (since they were possibly calculated using the newly edited row's previous value). As a special exception, whenever a calMode or trigMode row selection is changed, all calculated rows become unknown (since dependencies can't really be verified for those).

3. When a row's manual compute = button is tapped, that row is calculated and any rows it chains to are also calculated. Any variables used in those calculations are then marked known, as the user apparently considers their current values, whatever they are, to be valid to use to calculate with.

4. Whenever step 2 (editing a row) or step 3 (manually computing a row) is completed, an auto calc check is activated. This looks for any row which can be calculated (has a formula) and is currently unknown (perhaps just now marked unknown due to step 2) and for which all the variables it needs for its formula are known (perhaps just now marked known due to step 2 or 3, or even a just-performed prior auto calc). When it finds such a row it's calculated and marked known, and any rows it chains to are also calculated and marked known. This search and calculate is repeated until no more qualifying rows are found to calculate.

Solving Auto Calculate Problems

Ever have a template where it calculates correctly the first time but if you change a number it won't recalculate and even the equals button indicates it has been calculated?

We made a decision with powerOne that only the formulas with changed variables are re-calculated. In other words, we don't determine which rows need to be re-calculated after every computation; just when a row is changed via new input. Let's use a simple example to illustrate:

"A" "" a::
"B" "" b::
"C" "" c:b+a ::
"D" "" d:c+2::

Enter 5 for A and 6 for B and C and D will calculate the first time. That's because C and D are not known yet so they both calculate. Now change A to 7. Notice that C will re-calculate but D did not. That's because the changed variable, A, is not in the formula for D so it didn't know to re-calculate.

There are really three ways to combat this problem:

1. Re-write the entire formula, which can be hard to verify and fix with long or complicated equations.

"A" "" a::
"B" "" b::
"C" "" c:b+a ::
"D" "" d:(a+b)+2::

2. Use macros, which simplify the formulas. The same as above but written with macros:

[macros_begin]
  formula:a+b ::
[macros_end]

"A" "" a::
"B" "" b::
"C" "" c:formula ::
"D" "" d:formula+2::

3. Use chaining. Chaining tells the engine when I am done calculate C also calculate D.

"A" "" a::
"B" "" b::
"C" "" c,d:b+a ::
"D" "" d:c+2::

4. Fake it out. Use the potentially changed variable but do so without it impacting the result.

"A" "" a::
"B" "" b::
"C" "" c:b+a ::
"D" "" d:c+2+(a-a+b-b)::