Rule Cascade
LearnValues and types

Decimal numbers

Arithmetic is decimal, so 0.1 + 0.2 is 0.3. Every number that leaves an expression has at most 15 significant digits.

Inside an expression, arithmetic is decimal, not binary floating point. So 0.1 + 0.2 is exactly 0.3, in every language. Every number that leaves an expression — a computed value, a message argument, a command payload — is rounded half even to 15 significant digits. That is the most a JSON number keeps through any parser unchanged.

Syntax

numbers
{ op: add, args: [0.1, 0.2] }               # 0.3, exactly
{ op: div, args: [{ var: data.price }, 3] } # leaves the expression with 15 significant digits
StepPrecision
A number written in a ruleset or a requestThe nearest IEEE 754 double, read as its shortest decimal
Arithmetic and comparisons inside an expressionDecimal, 34 significant digits, round half even
A number leaving the expressionRounded half even to 15 significant digits

Example

The total is 0.1 plus 0.2, and the discount is a third of 0.1. The validation rule checks that 0.1 + 0.2 equals 0.3; it passes.

numbers.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.numbers, version: 1.0.0, title: "Decimal numbers" }
scope:
  - { level: organization, id: learn }
entities:
  Order:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
  - id: order.total.compute
    kind: compute
    target: { entity: Order, field: /total }
    operations: [create]
    assign:
      - { field: /total, value: { op: add, args: [{ var: data.price }, { var: data.shipping }] }, mode: always }
      - { field: /discount, value: { op: div, args: [{ var: data.price }, 3] }, mode: always }
  - id: order.total.exact
    kind: validation
    target: { entity: Order, field: /total }
    operations: [create]
    assert: { op: eq, args: [{ op: add, args: [0.1, 0.2] }, 0.3] }
    severity: error
    finding: { code: LRN-NUM-001, message: order.notDecimal }
messages:
  en:
    order.notDecimal: "0.1 + 0.2 is not 0.3 here."
tests:
  - name: 0.1 plus 0.2 is exactly 0.3, and a third has 15 digits
    entity: Order
    operation: create
    given:
      data: { price: 0.1, shipping: 0.2 }
    expect:
      decision: allow
      findings: []
      effects:
        - { type: value, field: /total, value: 0.3 }
        - { type: value, field: /discount, value: 0.0333333333333333 }
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "price": 0.1,
    "shipping": 0.2
  }
}

Result, from the engine

Decisionallow0 findings, server channel

  • computed value /total = 0.3
  • computed value /discount = 0.0333333333333333
Try it YourselfOpens this ruleset and request in the playground. Nothing to install.

Common mistakes

  • Sending identifiers as numbers. An order number with 17 digits does not survive a double. Send identifiers as strings.
  • Writing numbers with more than 15 significant digits in a ruleset. The checker reports them (NUMBER_NOT_PORTABLE).
  • Dividing by zero. div and mod with a zero divisor are evaluation errors.

Exercise

Compute /total as the price times the quantity. Test it with 3 items at 19.99: the total is exactly 59.97.

Hint

Replace the compute rule's value with mul of data.price and data.quantity, and expect the value 59.97 in the golden test's effects.

Show answer
numbers.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.numbers, version: 1.0.0, title: "Decimal numbers" }
scope:
  - { level: organization, id: learn }
entities:
  Order:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
  - id: order.total.compute
    kind: compute
    target: { entity: Order, field: /total }
    operations: [create]
    assign:
      - { field: /total, value: { op: mul, args: [{ var: data.price }, { var: data.quantity }] }, mode: always }
tests:
  - name: three items at 19.99 cost exactly 59.97
    entity: Order
    operation: create
    given:
      data: { price: 19.99, quantity: 3 }
    expect:
      decision: allow
      findings: []
      effects:
        - { type: value, field: /total, value: 59.97 }
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "price": 19.99,
    "quantity": 3
  }
}

Result, from the engine

Decisionallow0 findings, server channel

  • computed value /total = 59.97
Course overview

On this page