There is no support for floating point or decimal numbers in Numscript, which will always make sure non integer values resulting from monetary computations are balanced.
Practically, this means appropriately distributing the non integer allocation remainder to accounts. Numscript works by flooring any computed amount and subsequently spreading the remaining amount as fairly as possible starting from top to bottom.
In the example below:
send [COIN 99] (
source = @world
destination = {
50% to @rider
50% to @taxes
}
)
The @rider account will receive COIN 50 and the @taxes account COIN 49. The opposite can be achieved by reversing the order of destinations:
send [COIN 99] (
source = @world
destination = {
50% to @taxes
50% to @rider
}
)
In a more complex example below, we are splitting 99 into 5 which would result in 19.8 allocated to each account. Numscript will first allocate 19 to every account, then attempt to distribute the remaining 4 evenly starting from @a:
send [COIN 99] (
source = @world
destination = {
1/5 to @a
1/5 to @b
1/5 to @c
1/5 to @d
1/5 to @e
}
)
Which will resolve into the following postings:
[
{
"source": "world",
"destination": "a",
"amount": 20,
"asset": "COIN"
},
{
"source": "world",
"destination": "b",
"amount": 20,
"asset": "COIN"
},
{
"source": "world",
"destination": "c",
"amount": 20,
"asset": "COIN"
},
{
"source": "world",
"destination": "d",
"amount": 20,
"asset": "COIN"
},
{
"source": "world",
"destination": "e",
"amount": 19,
"asset": "COIN"
}
]
Fixed fees and allocation order
When combining fixed amounts with percentage-based allocations, the order of destinations matters due to the multi-pass resolution mechanism.
The problem
Consider a transaction splitting funds between a payment provider (fixed fee + percentage), a franchise fee (percentage), and a store (remaining):
send [AUD/2 1999] (
source = @world
destination = {
7/1999 to @payment_provider
0.6% to @payment_provider
0.5% to @franchise_fee
remaining to @store
}
)
You might expect @payment_provider to receive exactly 7 cents as a fixed fee, but it actually receives 8 cents. This happens because of the two-pass allocation mechanism.
How multi-pass allocation works
First pass - Numscript allocates whole amounts:
7/1999 * 1999 = 7 → @payment_provider
0.6% * 1999 = 11.994 → floors to 11 for @payment_provider (keeps 0.994 aside)
0.5% * 1999 = 9.995 → floors to 9 for @franchise_fee (keeps 0.995 aside)
remaining = 1999 - 7 - 11 - 9 - ceil(0.994 + 0.995) = 1970 → @store
Second pass - Numscript distributes the remaining fragments:
- Total distributed:
1999 - 2 = 1997
- Remaining to distribute:
2 cents
- Distribution is top to bottom: first position (
@payment_provider) gets +1, second position (@payment_provider again) gets +1
Result: @payment_provider receives 7 + 1 = 8 cents instead of the expected 7.
The solution
Move the fixed fee after the percentage allocations. Since percentage computations generate at most 1 cent fragment each, placing them first ensures they absorb the remainder:
send [AUD/2 1999] (
source = @world
destination = {
0.6% to @payment_provider
0.5% to @franchise_fee
7/1999 to @payment_provider
remaining to @store
}
)
Now the percentages receive any extra cents, and the fixed fee remains exactly 7 cents.
When mixing fixed amounts and percentages, place percentage-based allocations before fixed amounts to ensure fixed fees remain exact.