0
\$\begingroup\$

So I'm working on a Civ like game right now and want to have a setting how taxes are split in gold income, science and luxury goods.

private double[] rate = new double[] { 0.4, 0.6, 0.0 }; 

now I wanted to add a methode to change one of the values to a higher or lower value but maintain the overall sum of 1.0 (100%) but I'm stuck here with how to take or add values evenly from the other two rates

    public void setRate (int index, double r) {
    if (r < 0.0) {
        throw new IllegalArgumentException("Invalid Rate");
    }

    double diff = r - rate[index];
    if (diff > 0) {

        // ?

    } else if (diff < 0) {

        // ?

    }       
    rate[index] = r;
}

EDIT for amitp:

public void setRate (int index, double r) {
    if (rate[index] = 1.0) {
         for (int i = 0; i < 3; i++) {
              if (i != index) {
                  rate[i] = (rate[index] - r) / 2;
             }
        }
    } else { 
        for (int i = 0; i < 3; i++) {
            if (i != index) {
                rate[i] = rate[i] * (1.0 - r) / (1.0 - rate[index]);
            }
        rate[index] = r;
        }
    }
\$\endgroup\$

3 Answers 3

1
\$\begingroup\$

I think the simplest approach is to rescale the values.

The values other than the one you're setting currently sum to 1.0 - rate[index]. You want them to sum to 1.0 - r. You can therefore scale each of them by (1.0 - new_value) / (1.0 - old_value) to preserve the sum.

public void setRate (int index, double r) {
    for (int i = 0; i < 3; i++) {
        if (i != index) {
            rate[i] = rate[i] * (1.0 - r) / (1.0 - rate[index]);
        }
    }
    rate[index] = r;
}

This solution avoids:

  1. Different code for positive and negative changes
  2. Having to handle underflow (something + diff going below 0.0) and overflow (something + diff going above 1.0)
  3. A value going from zero to non-zero, or vice versa. If the player set something at 0, then they probably want to keep it there.

Side note: a geometric interpretation is that your three values are coordinates in a cube. You constrain x + y + z = 1.0 so that puts you on an equilateral triangle embedded in the cube. Thinking about it this way helped me come up with the above solution.

\$\endgroup\$
3
  • 2
    \$\begingroup\$ Note that this solution can still fail, if 1.0 - rate[index] is 0.0. That will happen when 100% of the resource is assigned to one value, and you try to take away from it. I guess you'd have to handle this special case, probably by evenly distributing the remainder to the other values. If you treat 0 values as epsilon instead of 0.0 then it will work out, I think. \$\endgroup\$
    – amitp
    Jul 12, 2014 at 17:42
  • \$\begingroup\$ Would something like this work to write before the for loop? Look at the Edit. \$\endgroup\$
    – user49078
    Jul 12, 2014 at 18:11
  • \$\begingroup\$ That looks like it should work. A unit test for this function should include cases where rates before and after are 0.0 and 1.0. \$\endgroup\$
    – amitp
    Jul 12, 2014 at 21:07
0
\$\begingroup\$

You could divide diff by two and then subtract it or add it accordingly to the other two.

double diff = r - rate[index];
double change = diff/2 * -1; //we need to reverse the operation

for(int i = 0 ; i < 3; i++)
{
    if(index != i) rate[i] += change;
}

Still you should be careful for number precision errors. For that you could use an int[] where all values sum up to 100.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You'll also need to handle upper and lower bounds. For example, if you start with {0.4, 0.6, 0.0} and want to increase the first to 0.5, then =change= will be -0.05. You'll end up with {0.5, 0.55, -0.05}. \$\endgroup\$
    – amitp
    Jul 12, 2014 at 17:06
0
\$\begingroup\$

One way to do this is to add to the indexes' value by its self multiplied by diff. rate[i] = rate[i] + rate[i]*diff where i is the other to values other than index

    double d = 1 - rate[index]       
    double diff = rate[index] - r;
    rate[index] = r;
    index = (index + 1) % 3;           // increment and wrap around index
    rate[index] += diff*rate[index]/d;
    index = (index + 1) % 3;           // increment and wrap around index
    rate[index] += diff*rate[index]/d;

Hope that helps.

\$\endgroup\$
2
  • \$\begingroup\$ Say I want to raise gold income from 0% to 50% But with your solution I would add 25% to both science and luxury goods and not raise the gold income (or decrease it). Also this only works when the index is 0. If I want to change the luxury the index is 2. Then I would add 1, have 3 and get an Exception. \$\endgroup\$
    – user49078
    Jul 12, 2014 at 11:42
  • \$\begingroup\$ You are correct diff should be defined as follows diff = rate[index] - r. Also I didn't include it but right after diff is define the new rate from which ever index should be set with the following statement rate[index] = r. The index should never equal 3 because any sum of index + 1 greater than 3 should result in a integer between 0 to 2 do to the modulus operator, %. I also forgot some semicolons. I just edited the code and it should would now when put inside your method. \$\endgroup\$
    – Andi
    Jul 13, 2014 at 19:48

You must log in to answer this question.