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.

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

Input Value: Scale:

Range Calculator

Scale:

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 * scale

JavaScript

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