I need to calculate the arc tan value from my Blackberry Java app. Unfortunately, the blackberry 4.2 api doesn't have the Math.atan() function. Version 4.6 of the Blackberry JDE has it, but not 4.2.
Does anyone know of a workaround to calculate atan?
From Arctan in J2ME by Stephen Zimmerman:
// calculation functions
public class Calculation {
// Because J2ME has no floating point numbers,
// some sort of fixed point math is required.
// My implementation is simply to shift 10 places.
// for example, 1024 (>> 10) = 1
// and 512 (>> 10) = 0.5
public static final int[] AtanTable = { 0, 1, 2, 3, 5, 6, 7, 8, 10, 11, 12,
13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 26, 27, 28, 29,
30, 30,31, 32, 33, 34, 35, 36, 37, 37, 38, 39, 40, 40, 41,
42, 43, 43, 44, 45 };
// / returns angle 0->359 in degrees
public static int atan(int Y, int X) {
boolean swap = false;
int top = Math.abs(Y);
int bottom = Math.abs(X);
if (top > bottom) {
int btemp = bottom;
bottom = top;
top = btemp;
swap = true;
} else if (bottom == 0)
return -300;
// this should keep index inbounds [0, 45]
int index = (top * 45) / bottom;
int angle = AtanTable[index];
if (swap)
angle = 90 - angle;
// X & Y += 180
// X & !Y = ...90
// !X & Y = ... 270
if ((X < 0) && (Y < 0))
angle += 180;
else if (Y < 0) {
angle = 90 - angle;
angle += 270;
} else if (X < 0) {
angle = 90 - angle;
angle += 90;
}
if (angle == 360)
angle = 0;
return angle;
}
}
When all else fails, one could probably obtain a decent value by estimating the result of an infinite series of the arctan function.
The Wikipedia page on inverse trigonometic functions has a section on the infinite series of inverse trigonometric functions, including arctan. In order to obtain an estimate, one would carry out the infinite series until the desired precision is obtained.
As for the reason why the arctan function is not included, it is probably because the processor in the Blackberry isn't very powerful, and would take a lot of processor resources to perform the calculation.
Also, looking at the Blackberry JDE 4.2 API documentation, there appears to be a fixed-point math library called Fixed32 which offers two flavors of arctan. They perform the calculation with 32-bit integers, so they probably offer some performance advantages over performing floating-point arithmetic.
Here is the function I use (no guarantees that it is very fast):
/** Square root from 3 */
final static public double SQRT3 = 1.732050807568877294;
static public double atan(double x)
{
boolean signChange=false;
boolean Invert=false;
int sp=0;
double x2, a;
// check up the sign change
if(x<0.)
{
x=-x;
signChange=true;
}
// check up the invertation
if(x>1.)
{
x=1/x;
Invert=true;
}
// process shrinking the domain until x<PI/12
while(x>Math.PI/12)
{
sp++;
a=x+SQRT3;
a=1/a;
x=x*SQRT3;
x=x-1;
x=x*a;
}
// calculation core
x2=x*x;
a=x2+1.4087812;
a=0.55913709/a;
a=a+0.60310579;
a=a-(x2*0.05160454);
a=a*x;
// process until sp=0
while(sp>0)
{
a=a+Math.PI/6;
sp--;
}
// invertation took place
if(Invert) a=Math.PI/2-a;
// sign change took place
if(signChange) a=-a;
//
return a;
}
I had the same problem... the missing math functions can be found in the following package:
net.rim.device.api.util.MathUtilities
First implement the standard arctan(x) using Taylor series (as described at http://en.wikipedia.org/wiki/Inverse_trigonometric_functions#Infinite_series)
The do the following before calling arctan:
1) First do this check.
if (x == 0) {
return 0;
}
2) if |x| > 1, compute arctan(1/x) and finally subtract the result from Pi/2
3) if |x| is close to 1, compute arctan of the half angle using the half angle formula
arctan(x) = 2*arctan(x/(1+sqrt(1+x*x))). That is, first compute the half angle and then multiply the result by 2. Otherwise, for |x| close to 1, arctan converges very slowly.
Related
I'm very new to JavaFX and currently working on a rouge like game in 2D, which uses GUI made with JavaFX. The moving character is drawn on the canvas with GraphicContext2D. There is only one canvas, and I'd like to solve my problem without overlaying if possible. Canvas is added to Borderpane as center, Scene created with this Borderpane, for the Stage this Scene was set. Canvas is redrawn after moving with character on KeyEvent. I've already made the Stage resizable. Everything is running well, with the exception that I just cannot keep my moving character on the center in the window and when canvas is bigger than the window it can move out of this window.
How can I keep my dear moving character in the middle?
OK, here is a very, very simplified version, but with same problem, character does not stay in middle, it can run off from window then return as canvas is set greater than window.
public class Main extends Application {
String[][] map = new String[40][20];
int[] playerCoords = {3,10};
//colored.png downloaded from https://kenney.nl/assets/bit-pack
Image tileset = new Image("/colored.png", 815 * 2, 373 * 2, true, false);
Canvas canvas = new Canvas(
20 * 32,
40 * 32);
GraphicsContext context = canvas.getGraphicsContext2D();
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage stage){
BorderPane borderPane = new BorderPane();
borderPane.setCenter(canvas);
map[3][10] = "p";
refresh();
Scene scene = new Scene(borderPane);
scene.setOnKeyPressed(keyEvent -> {switch (keyEvent.getCode()) {
case UP:
this.movePlayer(true);
refresh();
break;
case DOWN:
this.movePlayer(false);
refresh();
break;}});
stage.setScene(scene);
stage.show();
}
void movePlayer(boolean up){
int direction = up ? -1 : 1;
int newPosRow = playerCoords[0] + direction;
if ( newPosRow >= 0 && newPosRow < map.length) {
map[playerCoords[0]][playerCoords[1]] = null;
playerCoords[0] = newPosRow;
map[playerCoords[0]][playerCoords[1]] = "p";
}
}
void refresh(){
context.setFill(Color.BLACK);
context.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
for (int i = 0; i < map.length; i++ ){
for (int j = 0; j < map[0].length; j++){
if (map[i][j] != null && map[i][j].equals("p")){
//player Tile 28, 0
context.drawImage(tileset, 952,0, 32,32,j * 32,i * 32, 32,32);
} else{
context.drawImage(tileset, 0,0, 32,32,j * 32,i * 32, 32,32);
}
}
}
}
}
Ok, so it seems there is not an easy JavaFX solution currently. But if someone knows one, thanks for sharing. :-)
In the meantime I thought about an algorythmic solution by transforming coords according to absolute and relative (player placed in middle) coordinates of the player. It's very far from perfect, it changes only the rowCoords of map, as the player can move only vertically in this example, and it works with a canvas not much greater than the window size. I needed to change only the refresh method. There is something I do not understand at the moment,
it works with substracting centerCol from player's row coord
int centerRow = map.length/2;
int centerCol = map[0].length/2;
int mapRowDiff = playerCoords[0] - centerCol;
not with substracting centerRow from player's row coord
int centerRow = map.length/2;
int centerCol = map[0].length/2;
int mapRowDiff = playerCoords[0] - centerRow;
But at least it started working.
And here is my refresh() with the transformation, it became a bit complex.
void refresh(){
context.setFill(Color.BLACK);
context.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
int centerRow = map.length/2;
int centerCol = map[0].length/2;
int mapRowDiff = playerCoords[0] - centerCol;
for (int i = 0; i < map.length; i++ ){
for (int j = 0; j < map[0].length; j++){
if (mapRowDiff >= 0
&& mapRowDiff < map.length) {
if (map[mapRowDiff][j] != null && map[mapRowDiff][j].equals("p")) {
//player Tile 28, 0
context.drawImage(tileset, 952, 0, 32, 32, j * 32, i * 32, 32, 32);
} else {
context.drawImage(tileset, 0, 0, 32, 32, j * 32, i * 32, 32, 32);
}
} else {
context.drawImage(tileset, 0, 34, 32, 32, j * 32, i * 32, 32, 32);
}
}
mapRowDiff++;
}
}
I have a question about the condition of "entering" the "if condition" in the below recursive binary search:
http://www.fredosaurus.com/notes-cpp/algorithms/searching/rbinarysearch.html
int rBinarySearch(int sortedArray[], int first, int last, int key) {
// function:
// Searches sortedArray[first]..sortedArray[last] for key.
// returns: index of the matching element if it finds key,
// otherwise -(index where it could be inserted)-1.
// parameters:
// sortedArray in array of sorted (ascending) values.
// first, last in lower and upper subscript bounds
// key in value to search for.
// returns:
// index of key, or -insertion_position -1
// if key is not in the array.
if (first <= last) {
int mid = (first + last) / 2; // compute mid point.
if (key == sortedArray[mid])
return mid; // found it.
else if (key < sortedArray[mid])
// Call ourself for the lower part of the array
return rBinarySearch(sortedArray, first, mid-1, key);
else
// Call ourself for the upper part of the array
return rBinarySearch(sortedArray, mid+1, last, key);
}
return -(first + 1); // failed to find key
}
Specifically, I have a question regarding the part if (first <= last).
I was trying to hand trace the step of the above recursion function. For example, I write down an array of say [2, 5, 7, 11, 21, 26, 27, 36, 37, 42] and let the key be 1, i.e. key = 1.
Then I have in the first recursion, I think my first = 0, and last = 9.
So mid = (0 + 9)/2 = 4.5 (but mid will be 4 since mid is assigned to be int).
So mid[4] > 1, so then I have the next recursion being (arr[], 0, 4-1=3, 1),..... and so on.....
However it seems that the case of last < first doesn't seem to appear. I am just wondering is it actually possible for the case of last < first to happen? (i.e.. is the condition if (first <=last) necessary?)
If it is necessary, could someone illustrate with a very simple example that I can hand trace with to understand that this condition is necessary?
thanks
That condition is the stopping criterion for the case if the element that you are looking for is not an element of the array that you are searching.
In your example, if you further exercise it, the recursive calls will be
// first, last
rBinarySearch(arr[], 0, 3, 1); // -> mid = 1 -> else if
rBinarySearch(arr[], 0, 0, 1); // -> mid = 0 -> else if
rBinarySearch(arr[], 0, -1, 1); // there you go: last < first (i.e. -1 < 0)
Me and my friend are trying to implement a paper and the last step requires solving a linear programming problem to get the final result. We are not so familiar with LP so i'd like to ask for your help.
Here's the function which is based on the PROFSET model
and here's the proposed constraints
(1)
(2)
where:
Pa and Qi are the binary decision variables
J are all the available categories
F are sets of frequent categories
Φ is the total number of selected categories
Constraint (1) actually says that Qi is 1 if category i is included in some itemset A where Pa = 1
Basically, we are trying to use some common open source lp solvers (like joptimizer) but we dont know
how to define those constraints, especially those that define set inclusion rules. Most of those solvers
seem to accept just inequalities.
So, do you have any idea about how to define those constraints? Maybe transform them to inequalities or
something? Any help would be appreciated.
Thank you
A constraint that is written as an equality can be also written as two inequalities.
e.g.
A*x=b is the same as
A*x<=b and A*x>=b
In order to write such a LP there are two ways.
To hardcode it, meaning writing everything in code for example in Java.
Write it the mathematical way in a "language" called AMPL: https://ampl.com/resources/the-ampl-book/ For the second way you don't really need to know a programming language. AMPL transforms magically your LP into code and feeds it to a solver e.g. commercial: CPLEX, Gurobi (academic license available) or open source: GLPK. AMPL provides also an online platform that you can provide your model as .mod file and data as .dat files.
If you still want to hardcode your LP GLPK has nice examples e.g. in JAVA:
public class Lp {
// Minimize z = -.5 * x1 + .5 * x2 - x3 + 1
//
// subject to
// 0.0 <= x1 - .5 * x2 <= 0.2
// -x2 + x3 <= 0.4
// where,
// 0.0 <= x1 <= 0.5
// 0.0 <= x2 <= 0.5
// 0.0 <= x3 <= 0.5
public static void main(String[] arg) {
glp_prob lp;
glp_smcp parm;
SWIGTYPE_p_int ind;
SWIGTYPE_p_double val;
int ret;
try {
// Create problem
lp = GLPK.glp_create_prob();
System.out.println("Problem created");
GLPK.glp_set_prob_name(lp, "myProblem");
// Define columns
GLPK.glp_add_cols(lp, 3);
GLPK.glp_set_col_name(lp, 1, "x1");
GLPK.glp_set_col_kind(lp, 1, GLPKConstants.GLP_CV);
GLPK.glp_set_col_bnds(lp, 1, GLPKConstants.GLP_DB, 0, .5);
GLPK.glp_set_col_name(lp, 2, "x2");
GLPK.glp_set_col_kind(lp, 2, GLPKConstants.GLP_CV);
GLPK.glp_set_col_bnds(lp, 2, GLPKConstants.GLP_DB, 0, .5);
GLPK.glp_set_col_name(lp, 3, "x3");
GLPK.glp_set_col_kind(lp, 3, GLPKConstants.GLP_CV);
GLPK.glp_set_col_bnds(lp, 3, GLPKConstants.GLP_DB, 0, .5);
// Create constraints
// Allocate memory
ind = GLPK.new_intArray(3);
val = GLPK.new_doubleArray(3);
// Create rows
GLPK.glp_add_rows(lp, 2);
// Set row details
GLPK.glp_set_row_name(lp, 1, "c1");
GLPK.glp_set_row_bnds(lp, 1, GLPKConstants.GLP_DB, 0, 0.2);
GLPK.intArray_setitem(ind, 1, 1);
GLPK.intArray_setitem(ind, 2, 2);
GLPK.doubleArray_setitem(val, 1, 1.);
GLPK.doubleArray_setitem(val, 2, -.5);
GLPK.glp_set_mat_row(lp, 1, 2, ind, val);
GLPK.glp_set_row_name(lp, 2, "c2");
GLPK.glp_set_row_bnds(lp, 2, GLPKConstants.GLP_UP, 0, 0.4);
GLPK.intArray_setitem(ind, 1, 2);
GLPK.intArray_setitem(ind, 2, 3);
GLPK.doubleArray_setitem(val, 1, -1.);
GLPK.doubleArray_setitem(val, 2, 1.);
GLPK.glp_set_mat_row(lp, 2, 2, ind, val);
// Free memory
GLPK.delete_intArray(ind);
GLPK.delete_doubleArray(val);
// Define objective
GLPK.glp_set_obj_name(lp, "z");
GLPK.glp_set_obj_dir(lp, GLPKConstants.GLP_MIN);
GLPK.glp_set_obj_coef(lp, 0, 1.);
GLPK.glp_set_obj_coef(lp, 1, -.5);
GLPK.glp_set_obj_coef(lp, 2, .5);
GLPK.glp_set_obj_coef(lp, 3, -1);
// Write model to file
// GLPK.glp_write_lp(lp, null, "lp.lp");
// Solve model
parm = new glp_smcp();
GLPK.glp_init_smcp(parm);
ret = GLPK.glp_simplex(lp, parm);
// Retrieve solution
if (ret == 0) {
write_lp_solution(lp);
} else {
System.out.println("The problem could not be solved");
}
// Free memory
GLPK.glp_delete_prob(lp);
} catch (GlpkException ex) {
ex.printStackTrace();
ret = 1;
}
System.exit(ret);
}
/**
* write simplex solution
* #param lp problem
*/
static void write_lp_solution(glp_prob lp) {
int i;
int n;
String name;
double val;
name = GLPK.glp_get_obj_name(lp);
val = GLPK.glp_get_obj_val(lp);
System.out.print(name);
System.out.print(" = ");
System.out.println(val);
n = GLPK.glp_get_num_cols(lp);
for (i = 1; i <= n; i++) {
name = GLPK.glp_get_col_name(lp, i);
val = GLPK.glp_get_col_prim(lp, i);
System.out.print(name);
System.out.print(" = ");
System.out.println(val);
}
}}
I've been trying to transform some of my code to pure functions to learn how to use Kotlin in a functional way, with this simple snippet of code I can't think of any way to make my calculateFibonacci function a pure function.
I'm aware of a potentially recursive solution but what about a potential stack overflow, does Kotlin implement Tail Call Optimization?
Example:
val fibonacciValues = hashMapOf<Int, BigInteger>(0 to BigInteger.ONE, 1 to BigInteger.ONE);
// * TODO investigate how to do dynamic programming with a pure function ** //
private fun calculateFibonacci(n: Int): BigInteger? {
if (fibonacciValues.contains(n)) {
return fibonacciValues.get(n)
} else {
val f = calculateFibonacci(n - 2)!!.add(calculateFibonacci(n - 1))
fibonacciValues.put(n, f)
return f
}
}
For the whole snippet I uploaded this gist:
https://gist.github.com/moxi/e30f8e693bf044e8b6b80f8c05d4ac12
The whole thing is about breaking out of the imperative approach and thinking in terms of sequence manipulation.
In the case of the Fibonacci Sequence, it might be tricky because it's very tempting to think of it as a sequence of Ints but it gets much easier if you think of it as a sequence of pairs (from which you eventually derive a sequence of Ints)
So, you could create an infinite sequence of pairs where the next pair is defined as the second element of the previous pair and a sum of elements in a previous pair:
generateSequence(1 to 1) { it.second to it.first + it.second }
.map { it.first }
And yes, you can utilize the Tail Call Optimization by marking your method with the tailrec keyword - no worries about the stack overflow. You just apply it before the fun keyword:
fun fibonacciAt(n: Int) = {
tailrec fun fibonacciAcc(n: Int, a: Long, b: Long): Long {
return when (n == 0) {
true -> b
false -> fibonacciAcc(n - 1, a + b, a)
}
}
fibonacciAcc(n, 1, 0)
}
Here is more info about the Tail Recursion in Kotlin.
Homegrown:
fun fib(i: Int): Int {
tailrec fun go(k: Int, p: Int, c: Int): Int {
return if (k == 0) p
else go(k - 1, c, p + c)
}
return go(i, 0, 1)
}
generateSequence actually shows a Fibonacci implementation as example.
fun fibonacci(): Sequence<Int> {
// fibonacci terms
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, ...
return generateSequence(Pair(0, 1), { Pair(it.second, it.first + it.second) }).map { it.first }
}
println(fibonacci().take(10).toList()) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
does Kotlin implements Tail Call Optimization
Yes, there is tailrec keyword for that.
So I am trying to figure out how to take a range of numbers and scale the values down to fit a range. The reason for wanting to do this is that I am trying to draw ellipses in a java swing jpanel. I want the height and width of each ellipse to be in a range of say 1-30. I have methods that find the minimum and maximum values from my data set, but I won't have the min and max until runtime. Is there an easy way to do this?
Let's say you want to scale a range [min,max] to [a,b]. You're looking for a (continuous) function that satisfies
f(min) = a
f(max) = b
In your case, a would be 1 and b would be 30, but let's start with something simpler and try to map [min,max] into the range [0,1].
Putting min into a function and getting out 0 could be accomplished with
f(x) = x - min ===> f(min) = min - min = 0
So that's almost what we want. But putting in max would give us max - min when we actually want 1. So we'll have to scale it:
x - min max - min
f(x) = --------- ===> f(min) = 0; f(max) = --------- = 1
max - min max - min
which is what we want. So we need to do a translation and a scaling. Now if instead we want to get arbitrary values of a and b, we need something a little more complicated:
(b-a)(x - min)
f(x) = -------------- + a
max - min
You can verify that putting in min for x now gives a, and putting in max gives b.
You might also notice that (b-a)/(max-min) is a scaling factor between the size of the new range and the size of the original range. So really we are first translating x by -min, scaling it to the correct factor, and then translating it back up to the new minimum value of a.
Here's some JavaScript for copy-paste ease (this is irritate's answer):
function scaleBetween(unscaledNum, minAllowed, maxAllowed, min, max) {
return (maxAllowed - minAllowed) * (unscaledNum - min) / (max - min) + minAllowed;
}
Applied like so, scaling the range 10-50 to a range between 0-100.
var unscaledNums = [10, 13, 25, 28, 43, 50];
var maxRange = Math.max.apply(Math, unscaledNums);
var minRange = Math.min.apply(Math, unscaledNums);
for (var i = 0; i < unscaledNums.length; i++) {
var unscaled = unscaledNums[i];
var scaled = scaleBetween(unscaled, 0, 100, minRange, maxRange);
console.log(scaled.toFixed(2));
}
0.00, 18.37, 48.98, 55.10, 85.71, 100.00
Edit:
I know I answered this a long time ago, but here's a cleaner function that I use now:
Array.prototype.scaleBetween = function(scaledMin, scaledMax) {
var max = Math.max.apply(Math, this);
var min = Math.min.apply(Math, this);
return this.map(num => (scaledMax-scaledMin)*(num-min)/(max-min)+scaledMin);
}
Applied like so:
[-4, 0, 5, 6, 9].scaleBetween(0, 100);
[0, 30.76923076923077, 69.23076923076923, 76.92307692307692, 100]
For convenience, here is Irritate's algorithm in a Java form. Add error checking, exception handling and tweak as necessary.
public class Algorithms {
public static double scale(final double valueIn, final double baseMin, final double baseMax, final double limitMin, final double limitMax) {
return ((limitMax - limitMin) * (valueIn - baseMin) / (baseMax - baseMin)) + limitMin;
}
}
Tester:
final double baseMin = 0.0;
final double baseMax = 360.0;
final double limitMin = 90.0;
final double limitMax = 270.0;
double valueIn = 0;
System.out.println(Algorithms.scale(valueIn, baseMin, baseMax, limitMin, limitMax));
valueIn = 360;
System.out.println(Algorithms.scale(valueIn, baseMin, baseMax, limitMin, limitMax));
valueIn = 180;
System.out.println(Algorithms.scale(valueIn, baseMin, baseMax, limitMin, limitMax));
90.0
270.0
180.0
Here's how I understand it:
What percent does x lie in a range
Let's assume you have a range from 0 to 100. Given an arbitrary number from that range, what "percent" from that range does it lie in? This should be pretty simple, 0 would be 0%, 50 would be 50% and 100 would be 100%.
Now, what if your range was 20 to 100? We cannot apply the same logic as above (divide by 100) because:
20 / 100
doesn't give us 0 (20 should be 0% now). This should be simple to fix, we just need to make the numerator 0 for the case of 20. We can do that by subtracting:
(20 - 20) / 100
However, this doesn't work for 100 anymore because:
(100 - 20) / 100
doesn't give us 100%. Again, we can fix this by subtracting from the denominator as well:
(100 - 20) / (100 - 20)
A more generalized equation for finding out what % x lies in a range would be:
(x - MIN) / (MAX - MIN)
Scale range to another range
Now that we know what percent a number lies in a range, we can apply it to map the number to another range. Let's go through an example.
old range = [200, 1000]
new range = [10, 20]
If we have a number in the old range, what would the number be in the new range? Let's say the number is 400. First, figure out what percent 400 is within the old range. We can apply our equation above.
(400 - 200) / (1000 - 200) = 0.25
So, 400 lies in 25% of the old range. We just need to figure out what number is 25% of the new range. Think about what 50% of [0, 20] is. It would be 10 right? How did you arrive at that answer? Well, we can just do:
20 * 0.5 = 10
But, what about from [10, 20]? We need to shift everything by 10 now. eg:
((20 - 10) * 0.5) + 10
a more generalized formula would be:
((MAX - MIN) * PERCENT) + MIN
To the original example of what 25% of [10, 20] is:
((20 - 10) * 0.25) + 10 = 12.5
So, 400 in the range [200, 1000] would map to 12.5 in the range [10, 20]
TLDR
To map x from old range to new range:
OLD PERCENT = (x - OLD MIN) / (OLD MAX - OLD MIN)
NEW X = ((NEW MAX - NEW MIN) * OLD PERCENT) + NEW MIN
I came across this solution but this does not really fit my need. So I digged a bit in the d3 source code. I personally would recommend to do it like d3.scale does.
So here you scale the domain to the range. The advantage is that you can flip signs to your target range. This is useful since the y axis on a computer screen goes top down so large values have a small y.
public class Rescale {
private final double range0,range1,domain0,domain1;
public Rescale(double domain0, double domain1, double range0, double range1) {
this.range0 = range0;
this.range1 = range1;
this.domain0 = domain0;
this.domain1 = domain1;
}
private double interpolate(double x) {
return range0 * (1 - x) + range1 * x;
}
private double uninterpolate(double x) {
double b = (domain1 - domain0) != 0 ? domain1 - domain0 : 1 / domain1;
return (x - domain0) / b;
}
public double rescale(double x) {
return interpolate(uninterpolate(x));
}
}
And here is the test where you can see what I mean
public class RescaleTest {
#Test
public void testRescale() {
Rescale r;
r = new Rescale(5,7,0,1);
Assert.assertTrue(r.rescale(5) == 0);
Assert.assertTrue(r.rescale(6) == 0.5);
Assert.assertTrue(r.rescale(7) == 1);
r = new Rescale(5,7,1,0);
Assert.assertTrue(r.rescale(5) == 1);
Assert.assertTrue(r.rescale(6) == 0.5);
Assert.assertTrue(r.rescale(7) == 0);
r = new Rescale(-3,3,0,1);
Assert.assertTrue(r.rescale(-3) == 0);
Assert.assertTrue(r.rescale(0) == 0.5);
Assert.assertTrue(r.rescale(3) == 1);
r = new Rescale(-3,3,-1,1);
Assert.assertTrue(r.rescale(-3) == -1);
Assert.assertTrue(r.rescale(0) == 0);
Assert.assertTrue(r.rescale(3) == 1);
}
}
I sometimes find a variation of this useful.
Wrapping the scale function in a class so that I do not need to pass around the min/max values if scaling the same ranges in several places
Adding two small checks that ensures that the result value stays within the expected range.
Example in JavaScript:
class Scaler {
constructor(inMin, inMax, outMin, outMax) {
this.inMin = inMin;
this.inMax = inMax;
this.outMin = outMin;
this.outMax = outMax;
}
scale(value) {
const result = (value - this.inMin) * (this.outMax - this.outMin) / (this.inMax - this.inMin) + this.outMin;
if (result < this.outMin) {
return this.outMin;
} else if (result > this.outMax) {
return this.outMax;
}
return result;
}
}
This example along with a function based version comes from the page https://writingjavascript.com/scaling-values-between-two-ranges
Based on Charles Clayton's response, I included some JSDoc, ES6 tweaks, and incorporated suggestions from the comments in the original response.
/**
* Returns a scaled number within its source bounds to the desired target bounds.
* #param {number} n - Unscaled number
* #param {number} tMin - Minimum (target) bound to scale to
* #param {number} tMax - Maximum (target) bound to scale to
* #param {number} sMin - Minimum (source) bound to scale from
* #param {number} sMax - Maximum (source) bound to scale from
* #returns {number} The scaled number within the target bounds.
*/
const scaleBetween = (n, tMin, tMax, sMin, sMax) => {
return (tMax - tMin) * (n - sMin) / (sMax - sMin) + tMin;
}
if (Array.prototype.scaleBetween === undefined) {
/**
* Returns a scaled array of numbers fit to the desired target bounds.
* #param {number} tMin - Minimum (target) bound to scale to
* #param {number} tMax - Maximum (target) bound to scale to
* #returns {number} The scaled array.
*/
Array.prototype.scaleBetween = function(tMin, tMax) {
if (arguments.length === 1 || tMax === undefined) {
tMax = tMin; tMin = 0;
}
let sMax = Math.max(...this), sMin = Math.min(...this);
if (sMax - sMin == 0) return this.map(num => (tMin + tMax) / 2);
return this.map(num => (tMax - tMin) * (num - sMin) / (sMax - sMin) + tMin);
}
}
// ================================================================
// Usage
// ================================================================
let nums = [10, 13, 25, 28, 43, 50], tMin = 0, tMax = 100,
sMin = Math.min(...nums), sMax = Math.max(...nums);
// Result: [ 0.0, 7.50, 37.50, 45.00, 82.50, 100.00 ]
console.log(nums.map(n => scaleBetween(n, tMin, tMax, sMin, sMax).toFixed(2)).join(', '));
// Result: [ 0, 30.769, 69.231, 76.923, 100 ]
console.log([-4, 0, 5, 6, 9].scaleBetween(0, 100).join(', '));
// Result: [ 50, 50, 50 ]
console.log([1, 1, 1].scaleBetween(0, 100).join(', '));
.as-console-wrapper { top: 0; max-height: 100% !important; }
I've taken Irritate's answer and refactored it so as to minimize the computational steps for subsequent computations by factoring it into the fewest constants. The motivation is to allow a scaler to be trained on one set of data, and then be run on new data (for an ML algo). In effect, it's much like SciKit's preprocessing MinMaxScaler for Python in usage.
Thus, x' = (b-a)(x-min)/(max-min) + a (where b!=a) becomes x' = x(b-a)/(max-min) + min(-b+a)/(max-min) + a which can be reduced to two constants in the form x' = x*Part1 + Part2.
Here's a C# implementation with two constructors: one to train, and one to reload a trained instance (e.g., to support persistence).
public class MinMaxColumnSpec
{
/// <summary>
/// To reduce repetitive computations, the min-max formula has been refactored so that the portions that remain constant are just computed once.
/// This transforms the forumula from
/// x' = (b-a)(x-min)/(max-min) + a
/// into x' = x(b-a)/(max-min) + min(-b+a)/(max-min) + a
/// which can be further factored into
/// x' = x*Part1 + Part2
/// </summary>
public readonly double Part1, Part2;
/// <summary>
/// Use this ctor to train a new scaler.
/// </summary>
public MinMaxColumnSpec(double[] columnValues, int newMin = 0, int newMax = 1)
{
if (newMax <= newMin)
throw new ArgumentOutOfRangeException("newMax", "newMax must be greater than newMin");
var oldMax = columnValues.Max();
var oldMin = columnValues.Min();
Part1 = (newMax - newMin) / (oldMax - oldMin);
Part2 = newMin + (oldMin * (newMin - newMax) / (oldMax - oldMin));
}
/// <summary>
/// Use this ctor for previously-trained scalers with known constants.
/// </summary>
public MinMaxColumnSpec(double part1, double part2)
{
Part1 = part1;
Part2 = part2;
}
public double Scale(double x) => (x * Part1) + Part2;
}