Percent / Basis Points → Decimal Fraction¶
What
Normalise a Rate column that mixes percent (2.5%), basis points
(25 bps) and the odd already-decimal legacy value (0.03) into one consistent
decimal fraction.
Synthetic / teaching example
The data here is constructed, not sourced
— sample.csv is hand-written rows covering each rate notation. The problem
class is real; the rows are not.
Why interesting¶
Finance writes the same rate two ways — 2.5% and 25 bps
are identical — and stores them as text with their unit glued on. Any
arithmetic (rate * principal) throws on the % / bps suffix, and mixing
percent and basis points in one column means a single divide-by-100 is wrong for
half the rows. You need to detect the notation per cell before converting.
flowchart TD
R["**Rate** cell"]
R -->|"contains %"| P["strip %, ÷ 100<br/><small>2.5% → 0.025</small>"]
R -->|"contains bps"| B["leading number, ÷ 10000<br/><small>25 bps → 0.0025</small>"]
R -->|"otherwise"| D["already a fraction, × 1<br/><small>0.03 → 0.03</small>"]
Problem class documented in. (sources for the problem class — not for the data)
- Basis point — 1 bp = 0.01% =
0.0001; rate tables routinely mix
%andbps.
The trick¶
(see inline comments in sample.json):
IF(CONTAINS([Rate], '%'), REPLACE([Rate], '%', '') / 100,
IF(CONTAINS([Rate], 'bps'), SPLIT_PART([Rate], ' ', 1) / 10000,
[Rate] * 1))
%form → strip the sign, divide by 100.bpsform → take the leading number, divide by 10000 (1 bp = 0.0001).- anything else → already a fraction,
* 1to normalise.
Final result¶
Two notations for the same thing land on the same fraction, and the legacy decimal passes straight through:
Every rate is now a plain fraction ready to multiply against a balance — no unit parsing in the consuming code.
Sample data¶
Run it with bxp-cli --config ./sample.json --template percent_to_fraction:
{
// Teaching example — synthetic data. A rate sheet where the same quantity is
// written two ways finance loves: percent ("2.5%") and basis points
// ("25 bps"), plus the occasional already-decimal legacy value. To compute
// with them you need one consistent fraction (0.025). The template detects
// the notation and normalises each to a decimal fraction.
conversion_templates: {
percent_to_fraction: {
data_dir: ".",
file_pattern_in: ".csv",
file_pattern_out: ".csvx",
input_schema: {
$product: "[Product]",
// THE TRICK — dispatch on the notation:
// "2.5%" → strip '%', divide by 100 → 0.025
// "25 bps" → take the number, divide by 10000 → 0.0025
// "0.03" → already a fraction, pass through (* 1 to normalise)
// (1 basis point = 0.01% = 0.0001, so bps ÷ 10000.)
$rate: "IF(CONTAINS([Rate], '%'), REPLACE([Rate], '%', '') / 100, IF(CONTAINS([Rate], 'bps'), SPLIT_PART([Rate], ' ', 1) / 10000, [Rate] * 1))"
},
row_rules: [ { when: "1 = 1", rows: [ {} ] } ],
output_schema: {
product: "$product",
rate_fraction: "$rate"
}
}
}
}