Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

v0 source pure

Package prorata splits an integer amount across weighted claimants without leaking or inventing a unit.

Readme View source

gno.land/p/moul/x/games/prorata/v0

A weighted split of an integer amount that adds up: Split, Share, Total.

1import "gno.land/p/moul/x/games/prorata/v0"
2
3prorata.Split(100, []int64{1, 2, 3})  // [17 33 50], and it sums to exactly 100
4prorata.Split(10, []int64{1, 1, 1})   // [4 3 3], the dust goes to the lowest index
5prorata.Share(100, 1, 3)              // 33, floored: "what would I get"

Every payout in a game is this operation: a pot, a roster, a weight each. Written the obvious way, amount*weight/total per claimant, it is wrong in two ways that only show up once there is real money in the contract.

The shares do not add up. Each one is rounded down, so their sum is short by up to one unit per claimant. That dust either sits in the realm forever or lands on whoever the loop happened to pay last, which is a rule nobody chose. Split distributes by largest remainder: floor every share, then hand the leftover units out one at a time to the claimants whose truncated fraction was largest, ties going to the lower index. The result sums to exactly the amount, and the rule is deterministic, so every node computes the same split and a Render of it does not change between calls.

amount*weight overflows an int64 long before either factor does, which turns a large payout negative. Nothing here ever computes that product: it splits the amount into whole multiples of the total and what is left over, so amount*weight/total becomes q*weight + r*weight/total with r already smaller than the total. A pot of 10^18 ugnot across billion-unit weights is exact, and what genuinely cannot fit returns ErrOverflow rather than wrapping.

Two smaller decisions, each with a test:

  • A zero weight is paid nothing, including no remainder unit. Somebody with no claim is not handed dust.
  • An empty roster, or one where every weight is zero, is an error, not an empty split. Silently returning nothing would strand the amount, and only the caller knows where it should go instead.

Nothing here reads the chain and nothing holds coins: the caller owns the roster and the custody.

Live game: r/moul/x/games/lastwords · render it at /r/moul/x/games/lastwords/v0.


Part of moul/gno-contracts — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage.

🧪 Highly experimental — potentially vibe-coded. Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: DISCLAIMER.

Overview

Package prorata splits an integer amount across weighted claimants without leaking or inventing a unit.

Every payout in a game is this operation: a pot, a roster, and a weight each. Written the obvious way, `amount*weight/total` per claimant, it is wrong in two ways that only show up with real money in the contract. The shares are each rounded down, so their sum is short of the amount by up to one unit per claimant, and that dust either accumulates in the contract forever or gets handed to whoever the code happens to pay last. And `amount*weight` overflows an int64 long before either factor does, which turns a large payout negative.

Split fixes both. It distributes by largest remainder: floor every share, then hand the leftover units out one at a time to the claimants whose truncated fraction was largest, ties going to the lower index. The result sums to EXACTLY the amount, every time, and the rule is deterministic, so every node computes the same split and a Render of it does not change between calls.

Nothing here reads the chain and nothing holds coins: the caller owns the roster and the custody. A game built on this package is at r/moul/x/games/lastwords(/r/moul/x/games/lastwords/v0).

Variables 1

var ErrNegativeAmount, ErrNegativeWeight, ErrNoWeight, ErrOverflow

 1var (
 2	// ErrNegativeAmount is returned when the amount to split is negative.
 3	ErrNegativeAmount = errors.New("prorata: amount must not be negative")
 4	// ErrNegativeWeight is returned when any weight is negative.
 5	ErrNegativeWeight = errors.New("prorata: weights must not be negative")
 6	// ErrNoWeight is returned when there is nobody to pay, or every weight is
 7	// zero. The caller must decide where the amount goes instead: silently
 8	// returning an empty split would strand it.
 9	ErrNoWeight = errors.New("prorata: total weight is zero, nothing to split across")
10	// ErrOverflow is returned when the arithmetic does not fit in an int64.
11	ErrOverflow = errors.New("prorata: weights overflow int64")
12)
source

Functions 3

func Share

1func Share(amount, weight, total int64) (int64, error)
source

Share returns what one weight is owed out of amount, rounded down. It is the "what would I get" query, and deliberately does NOT account for the remainder: use Split when the shares have to add up.

func Split

1func Split(amount int64, weights []int64) ([]int64, error)
source

Split divides amount across weights and returns one share per weight, in the same order. The shares sum to exactly amount.

A zero weight is allowed and receives nothing, including no remainder unit: somebody with no claim is not paid dust.

func Total

1func Total(weights []int64) (int64, error)
source

Total sums weights, refusing an int64 overflow rather than wrapping into a negative total that would make every share nonsense.

Imports 2

  • errors stdlib
  • sort stdlib

Source Files 4