mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 10:37:26 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			81 lines
		
	
	
		
			2.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			81 lines
		
	
	
		
			2.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package keeper
 | 
						|
 | 
						|
import (
 | 
						|
	"sort"
 | 
						|
 | 
						|
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
						|
)
 | 
						|
 | 
						|
// splitIntIntoWeightedBuckets divides an initial +ve integer among several buckets in proportion to the buckets' weights
 | 
						|
// It uses the largest remainder method: https://en.wikipedia.org/wiki/Largest_remainder_method
 | 
						|
// See also: https://stackoverflow.com/questions/13483430/how-to-make-rounded-percentages-add-up-to-100
 | 
						|
func splitIntIntoWeightedBuckets(amount sdk.Int, buckets []sdk.Int) []sdk.Int {
 | 
						|
	// Limit input to +ve numbers as algorithm hasn't been scoped to work with -ve numbers.
 | 
						|
	if amount.IsNegative() {
 | 
						|
		panic("negative amount")
 | 
						|
	}
 | 
						|
	if len(buckets) < 1 {
 | 
						|
		panic("no buckets")
 | 
						|
	}
 | 
						|
	for _, bucket := range buckets {
 | 
						|
		if bucket.IsNegative() {
 | 
						|
			panic("negative bucket")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 1) Split the amount by weights, recording whole number part and remainder
 | 
						|
 | 
						|
	totalWeights := totalInts(buckets...)
 | 
						|
	if !totalWeights.IsPositive() {
 | 
						|
		panic("total weights must sum to > 0")
 | 
						|
	}
 | 
						|
 | 
						|
	quotients := make([]quoRem, len(buckets))
 | 
						|
	for i := range buckets {
 | 
						|
		// amount * ( weight/total_weight )
 | 
						|
		q := amount.Mul(buckets[i]).Quo(totalWeights)
 | 
						|
		r := amount.Mul(buckets[i]).Mod(totalWeights)
 | 
						|
		quotients[i] = quoRem{index: i, quo: q, rem: r}
 | 
						|
	}
 | 
						|
 | 
						|
	// 2) Calculate total left over from remainders, and apportion it to buckets with the highest remainder (to minimize error)
 | 
						|
 | 
						|
	// sort by decreasing remainder order
 | 
						|
	sort.Slice(quotients, func(i, j int) bool {
 | 
						|
		return quotients[i].rem.GT(quotients[j].rem)
 | 
						|
	})
 | 
						|
 | 
						|
	// calculate total left over from remainders
 | 
						|
	allocated := sdk.ZeroInt()
 | 
						|
	for _, qr := range quotients {
 | 
						|
		allocated = allocated.Add(qr.quo)
 | 
						|
	}
 | 
						|
	leftToAllocate := amount.Sub(allocated)
 | 
						|
 | 
						|
	// apportion according to largest remainder
 | 
						|
	results := make([]sdk.Int, len(quotients))
 | 
						|
	for _, qr := range quotients {
 | 
						|
		results[qr.index] = qr.quo
 | 
						|
		if !leftToAllocate.IsZero() {
 | 
						|
			results[qr.index] = results[qr.index].Add(sdk.OneInt())
 | 
						|
			leftToAllocate = leftToAllocate.Sub(sdk.OneInt())
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return results
 | 
						|
}
 | 
						|
 | 
						|
type quoRem struct {
 | 
						|
	index int
 | 
						|
	quo   sdk.Int
 | 
						|
	rem   sdk.Int
 | 
						|
}
 | 
						|
 | 
						|
// totalInts adds together sdk.Ints
 | 
						|
func totalInts(is ...sdk.Int) sdk.Int {
 | 
						|
	total := sdk.ZeroInt()
 | 
						|
	for _, i := range is {
 | 
						|
		total = total.Add(i)
 | 
						|
	}
 | 
						|
	return total
 | 
						|
}
 |