Search code examples
c#unity-game-enginedivision

Split a float in pre-determined amount that adds up together to be the float


I am looking to create a script that basically does the following:-

  • Split a given float into x amounts of floats (particularly into different values)
  • Each value must be an increment of 0.05 (eg 2.65, 1.25, 18.50)
  • The x amounts of floats must add up to be the given float

IE it should look like this:

Total float = 2.50
Amount of splits = 2
New float array = [2, 0.50]

or

Total float = 4.00
Amount of splits = 4
New float array = [1, 2, 0.25, 0.75]

I have not been able to try much at the moment. I have been trying to think of a way to make it work but I am unsure of the best way to do it.

EDIT: I was able to make some progress but I have one small issue where some times if the number I'm using is too small, it will add a 0 or two in the list which I do not want.


public void SplitWinnings()
{
    splitAmounts = new float[loseThreshold - 1];
    while(splitAmounts.Sum() < totalWinAmount)
    {
        int rng = Random.Range(0, splitAmounts.Length);
        splitAmounts[rng] += 0.05f;
        splitAmounts[rng] = Mathf.Round(splitAmounts[rng] * 100f) / 100f;
    }
}


Solution

  • Your way using Linq Sum is extremely inefficient!

    You can already improve this a lot by a factor of about 3 by simply keeping track of the sum yourself directly without always iterating the entire array over and over again. Simply keep track of exactly the amount you add or remove from the array:

    public void SplitWinnings()
    {
        // Keep track of the sum yourself!
        // You already know how much you add each time!
        var sum = 0f;
    
        splitAmounts = new float[loseThreshold - 1];
        while(sum < totalWinAmount)
        {
            int rng = Random.Range(0, splitAmounts.Length);
            splitAmounts[rng] += minIncrement;
            // also keep track of the sum
            sum += minIncrement;
    
            float splitFloat = (Mathf.Round(splitAmounts[rng] * 100f) / 100f);
    
            if (splitFloat > 0)
            {
                // remove the previous value
                sum -= splitAmounts[rng];
    
                splitAmounts[rng] = splitFloat;
    
                // track the new value
                sum += splitFloat;
            }
        }
    }
    

    However, you still do a lot of rounding and might end up with invalid iterations which means throwing away performance.

    I would even go further and simply beforehand calculate the maximum required multiples of your 0.05 increment, iterate a fixed amount -> you know exactly how many elements you need to fill beforehand and then do all the rest of the calculation in int.

    Doing this I improved the time by a factor of 100!

    public void SplitWinnings()
    {
        // calculate how many of your minIncrements you need in total
        var totalMultiples = Mathf.RoundToInt(totalWinAmount / minIncrement);
        // while iterating will keep track of how many multiples you have remaining
        var remainingMultiples = totalMultiples;
    
        // Not sure why but you seem to require -1 here
        var splitCount = loseThreshold - 1;
        splitAmounts = new float[splitCount];
        
        // instead of while have a fixed amount of iterations
        // namely exactly for all return elements except the last one
        for(var i = 0; i < splitCount - 1; i++)
        { 
            // reducing the remaining array elements => save up at least 1 multiple for each array element
            var maxMultiples = remainingMultiples - (splitCount - i);
            // use at least one multiple per element
            // have to add +1 again since upper bound for int is exclusive but we want to use it up
            var multiples = Random.Range(1, maxMultiples + 1);
            
            // after calculating the multiples in int multiply again by the minIncrement to get according float value
            splitAmounts[i] = multiples * minIncrement;
            
            // and reduce the remaining multiples accordingly
            remainingMultiples -= multiples;
        }
        
        // The last item is just filled with whatever is left
        splitAmounts[splitCount - 1] = remainingMultiples * minIncrement;
    }
    

    See .Net Fiddle for comparing