log in
Grimoire: Diminishing Returns Formula
Introduction
In the course of developing Lost Souls, we've often needed a way to impose diminishing returns on various ratings. I've tried a lot of ways of doing this. Eventually, one formula emerged as generally superior to others, having a progression that fits game usage much better than most alternatives, being open-ended rather than imposing an absolute cap, and being adaptable to whatever numeric range we might want. I present this formula here.
I'll explain in detail what the formula does, and the math behind it, but if you like, you can skip directly to the calculator interfaces where you can take a look at the results it produces for individual input values and scales, see an overview of the results a scale produces for a range of typical input values, or get code for the formula in LPC, C, C#, Perl, PHP, Python, JavaScript, or Java as appropriate to your hosting server.
The formula works from the basis of a "scale number", which is a sort of starting point that determines how "tough" the scale you're using will be — how quickly it will begin diminishing. If your scale number is 20, for instance, then an input value of 20 will produce an output value of 20. This can be thought of as the first "performance level" of the formula. To get from there to the second "performance level", an output of 40, takes 20 units more than it took to get to the first level; so an input of 60 produces an output of 40. To get to the third level, an output of 60, takes an additional 60 units, for a total of 120. So the progression of levels goes:
20 (0 + 20) -> 20
60 (20 + 40) -> 40
120 (60 + 60) -> 60
200 (120 + 80) -> 80
300 (200 + 100) -> 100
etc.
This gives a steady, but not drastic, reduction in marginal output as input grows larger. The formula works just as well with any scale number you choose. For results that begin diminishing very quickly, one could use a scale number of 3, making the progression:
3 (0 + 3) -> 3
9 (3 + 6) -> 6
18 (9 + 9) -> 9
30 (18 + 12) -> 12
45 (30 + 15) -> 15
etc.
You can use this flexibility to tune your use of the formula for your needs in a particular situation.
Math
For those interested in the mathematics, the formula uses the sequence of triangular numbers: 1, 3, 6, 10, 15, 21, etc. As you can see, the divergence between the numbers increases by 1 at each step, giving us the basic mathematical behavior we want for this formula. To make use of it, we first determine what factor of the scale number we have as input:
factor = input_value / scale_number
Then we want to determine the position in the triangular number sequence that this factor occupies. The formula for a triangular number is n = p * (p + 1) / 2. Solving this for p, we wind up using:
position = (sqrt(8 * factor + 1) - 1) / 2
We then multiply this position by the scale number to get our output value.
output = position * scale_number
Sample Calculators
You can use these calculators to try out the behavior of the function with different input values and scale numbers.
Individual Value Calculator
Range Calculator
| Input | Output |
| 20 | |
| 50 | |
| 100 | |
| 200 | |
| 300 | |
| 500 | |
| 1000 |
Source Code Snippets
Use these source code snippets to implement the formula for your own applications. They are released into the public domain and are yours to use freely.
LPC
float diminishing_returns(mixed val, mixed scale) {
if(val < 0)
return -diminishing_returns(-val, scale);
float mult = val / to_float(scale);
float trinum = (sqrt(8.0 * mult + 1.0) - 1.0) / 2.0;
return trinum * scale;
}C
#include <math.h> /* needs to be compiled with -lm */
float diminishing_returns(float val, float scale) {
if(val < 0)
return -diminishing_returns(-val, scale);
float mult = val / scale;
float trinum = (sqrt(8.0 * mult + 1.0) - 1.0) / 2.0;
return trinum * scale;
}C#
public static double diminishing_returns(double val, double scale) {
if(val < 0)
return -diminishing_returns(-val, scale);
double mult = val / scale;
double trinum = (Math.Sqrt(8.0 * mult + 1.0) - 1.0) / 2.0;
return trinum * scale;
}Perl
sub diminishing_returns($$) {
my $val = shift;
my $scale = shift;
return -diminishing_returns(-$val, $scale)
if $val < 0;
my $mult = $val / $scale;
my $trinum = (sqrt(8.0 * $mult + 1.0) - 1.0) / 2.0;
return $trinum * $scale;
}PHP
function diminishing_returns($val, $scale) {
if($val < 0)
return -diminishing_returns(-$val, $scale);
$mult = $val / $scale;
$trinum = (sqrt(8.0 * $mult + 1.0) - 1.0) / 2.0;
return $trinum * $scale;
}Python
import math
def diminishing_returns(val, scale):
if val < 0:
return -diminishing_returns(-val, scale)
mult = val / float(scale)
trinum = (math.sqrt(8.0 * mult + 1.0) - 1.0) / 2.0
return trinum * scaleJavaScript
function diminishing_returns(val, scale) {
if(val < 0)
return -diminishing_returns(-val, scale);
var mult = val / scale;
var trinum = (Math.sqrt(8.0 * mult + 1.0) - 1.0) / 2.0;
return trinum * scale;
}Java
public static double diminishing_returns(double val, double scale) {
if(val < 0)
return -diminishing_returns(-val, scale);
double mult = val / scale;
double trinum = (Math.sqrt(8.0 * mult + 1.0) - 1.0) / 2.0;
return trinum * scale;
}