> ## Documentation Index
> Fetch the complete documentation index at: https://docs.canary.effectiveai.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Check messages: numbers and rounding

> RSL calculates with exact numbers. These messages concern precision, rounding and ranges.

*Generated from the `rslc` compiler.*

## E-QUANTUM

**A type name has one precision.**

A type such as `Money = Num {precision: 0.01}` gives its name one precision. This check guards a type name that two declarations give two different precisions.

A current rater gets an earlier message in its place. The compiler stops at the second declaration of a name with `E-UNRESOLVED`: `duplicate Types declaration 'Money'`. A guideline that declares `Premium` differently from the rater it uses gets `E-USE`.

Keep one declaration for each type name. Give a second precision its own name, such as `WholeMoney = Num {precision: 1}`.

## E-ROUND

**A rounding states its scale and mode.**

`round` takes three arguments: a value, a scale and a mode. The scale is a whole number of decimal places: `2` for cents, `0` for whole dollars, `-1` for tens. The mode is one of `half_up`, `half_down`, `half_even`, `up`, `down`, `ceiling` and `floor`.

Filings differ on how they round a tie, so RSL leaves the mode for you to state. A rounding that states its rule gives the filed premium on every request, including one that lands on half a cent.

**Example**

The premium calls `round` with a value and a scale, and leaves out the mode.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2)
        }
    }
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-ROUND bad/rater.rsl:26:50: round(x, increment) was removed; rounding names its scale and mode. Write round(x, SCALE, MODE) (half_up is the old rule: ties away from zero), or the mode the filing states. Rewrite an older rater in RSL 0.4 with this rslc
```

**Fix**

Add the mode the filing states as the third argument. `half_up` rounds a tie away from zero.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
    }
```

**Other causes**

* `round` has one argument, or more than three.
* The scale is an increment such as `0.01`; write the number of decimal places, `2`.
* The scale lies outside -1000 to 1000.
* The mode is a name outside the seven modes, such as `nearest`.
* `round_each: (scale, mode)` of a stepwise `sum` or `product` breaks one of the scale and mode rules above.
* `interpolate!` has `result: exact`; the result states a scale and a mode, and only `ratio:` and `step:` take `exact`.
* `interpolate!` has a negative `ratio:` scale; a ratio lies between 0 and 1, so its scale is 0 or more.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] = round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-FOLD-KEY

**A stepwise fold names its order.**

A stepwise fold rounds after every step: `product(self.drivers as d by d.number, d.factor, round_each: (3, half_up))`. It takes one element at a time and rounds the running result each time.

Rounding at every step makes the order of the elements matter. The `by` key fixes that order, so a request rates the same however it lists its drivers.

The check asks for `by` and `round_each` together, on a `sum` or a `product`. The key is a number or date input of the element, read as `d.number`. Each element of one list has its own key value, and the rater rejects a request that repeats one.

**Example**

The product rounds each step to three decimals and leaves out the `by` key.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        step rating {
            driver_factor: Factor [cite: manual:p4] =
                product(self.drivers as d, d.factor, round_each: (3, half_up)),
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.driver_factor, 2, half_up)
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-FOLD-KEY bad/rater.rsl:28:17: `round_each` needs `by v.key`: the key fixes the order of the per-step rounds. Write product(coll as v by v.key, v.factor, round_each: (4, half_up))
```

**Fix**

Add `by` with the input that orders the elements, here the driver number.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        step rating {
            driver_factor: Factor [cite: manual:p4] =
                product(self.drivers as d by d.number, d.factor, round_each: (3, half_up)),
            premium: Premium [cite: manual:p3] =
                round(self.payroll / 1000 * rate_per_1000 * self.driver_factor, 2, half_up)
```

**Other causes**

* A fold has `by` and lacks `round_each`; add the rounding, or delete `by` for an exact sum or product.
* `by` or `round_each` sits on a fold other than `sum` and `product`, such as `max`, `avg` or `count`.
* The key is a computed value such as `d.factor * 2`, or an attribute of another record such as `self.payroll`.
* `round_each` sits on a `sum` of plain values, such as `sum(self.payroll, 1, round_each: (3, half_up))`; a stepwise fold reads a list.
* `rank` has a `by` key; `rank` orders by the list's own `keyed by` field.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             drivers: List(Driver) where len(self) in [0, 10]
>         },
>         step rating {
>             driver_factor: Factor [cite: manual:p4] =
>                 product(self.drivers as d, d.factor, round_each: (3, half_up)),
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.driver_factor, 2, half_up)
>         }
>     },
>     Driver = {
>         inputs {
>             number: Num {precision: int} where self in [1, 99],
>             factor: Num {precision: 0.01} where self in [0.5, 2]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000],
>             drivers: List(Driver) where len(self) in [0, 10]
>         },
>         step rating {
>             driver_factor: Factor [cite: manual:p4] =
>                 product(self.drivers as d by d.number, d.factor, round_each: (3, half_up)),
>             premium: Premium [cite: manual:p3] =
>                 round(self.payroll / 1000 * rate_per_1000 * self.driver_factor, 2, half_up)
>         }
>     },
>     Driver = {
>         inputs {
>             number: Num {precision: int} where self in [1, 99],
>             factor: Num {precision: 0.01} where self in [0.5, 2]
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```

## E-COMPUTED-RANGE

**A calculation proves its declared range.**

A calculation states a promise about its value. A range reads `where self in [0, 100000]`, and a precision comes from a type such as `Premium = Num {precision: 0.01}`.

The compiler works out the range and precision of the body from the inputs it reads. The check asks the body to prove the promise for every request the rater accepts.

Calculations that read the value rely on the promise, for example as a divisor or a table key. A promise the body proves holds for every premium the rater returns.

**Example**

The premium declares a range up to 100,000. Payroll reaches 100,000,000 at a rate of 2 per 1,000, so the body reaches 200,000.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] where self in [0, 100000] =
                round(self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
```

```sh theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
rslc check bad/rater.rsl
```

```text theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
FAIL bad/rater.rsl (strict)
error: E-COMPUTED-RANGE bad/rater.rsl:27:17: `premium` declares range 0..100000, but its body has range 0..200000; narrow the inputs it reads, clamp the body with min/max, or delete the declared range
cost premium: 1 (each list of size 1: 1, 3: 1, 10: 1)
```

**Fix**

State the range the body reaches. A filing that caps the premium calls for a clamp: `min(round(…), 100000)`. A filing that limits payroll calls for a narrower range on the input.

```rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
        },
        step rating {
            premium: Premium [cite: manual:p3] where self in [0, 200000] =
                round(self.payroll / 1000 * rate_per_1000, 2, half_up)
        }
```

**Other causes**

* The body reads an input with an open range such as `[0, _]`, so the body has an open bound on that side.
* The calculation's type has a precision and the body is off that grid, such as a `Premium` of whole dollars rounded to cents. Round the body to the declared scale: `round(…, 0, half_up)`.

> [accordion] Complete files
>
> ```rsl bad/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] where self in [0, 100000] =
>                 round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```
>
> ```rsl good/rater.rsl theme={"languages":{"custom":["languages/rsl.tmLanguage.json"]}}
> spec {
>     rsl: "0.4.1",
>     carrier: "Example Mutual",
>     state: AZ,
>     product: "Staffing Liability",
>     effective_date: 2026-01-01
> }
>
> Sources = {
>     manual: Source = {
>         artifact: artifact("00000000-0000-4000-8000-000000000000"),
>         kind: pdf
>     }
> }
>
> Constants = {
>     rate_per_1000 [cite: manual:p3] = 2
> }
>
> Records = {
>     Policy = {
>         inputs {
>             payroll: Num {precision: int} where self in [0, 100000000]
>         },
>         step rating {
>             premium: Premium [cite: manual:p3] where self in [0, 200000] =
>                 round(self.payroll / 1000 * rate_per_1000, 2, half_up)
>         }
>     }
> }
>
> RatingOutcomes = {
>     premium: Policy.premium
> }
> ```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.