arbitrary datatype ratio converter - c++

I have following code:
template<typename I,typename O> O convertRatio(I input,
I inpMinLevel = std::numeric_limits<I>::min(),
I inpMaxLevel = std::numeric_limits<I>::max(),
O outMinLevel = std::numeric_limits<O>::min(),
O outMaxLevel = std::numeric_limits<O>::max() )
{
double inpRange = abs(double(inpMaxLevel - inpMinLevel));
double outRange = abs(double(outMaxLevel - outMinLevel));
double level = double(input)/inpRange;
return O(outRange*level);
}
the usage is something like this:
int value = convertRatio<float,int,-1.0f,1.0f>(0.5);
//value is around 1073741823 ( a quarter range of signed int)
the problem is for I=int and O=float with function default parameter:
float value = convertRatio<int,float>(123456);
the line double(inpMaxLevel - inpMinLevel) result is -1.0, and I expect it to be 4294967295 in float.
do you have any idea to do it better?
the base idea is just to convert a value from a range to another range with posibility of different data type.

Adding to romkyns answer, besides casting all values to doubles before casting to prevent overflows, your code returns wrong results when the lower bounds are distinct than 0, because you don't adjust the values appropiately. The idea is mapping the range [in_min, in_max] to the range [out_min, out_max], so:
f(in_min) = out_min
f(in_max) = out_max
Let x be the value to map. The algorithm is something like:
Map the range [in_min, in_max] to [0, in_max - in_min]. To do this, substract in_min from x.
Map the range [0, in_max - in_min] to [0, 1]. To do this, divide x by (in_max - in_min).
Map the range [0, 1] to [0, out_max - out_min]. To do this, multiply x by (out_max - out_min).
Map the range [0, out_max - out_min] to [out_min, out_max]. To do this, add out_min to x.
The following implementation in C++ does this (I will forget the default values to make the code clearer:
template <class I, class O>
O convertRatio(I x, I in_min, I in_max, O out_min, O out_max) {
const double t = ((double)x - (double)in_min) /
((double)in_max - (double)in_min);
const double res = t * ((double)out_max - (double)out_min) + out_min;
return O(res);
}
Notice that I didn't took the absolute value of the range sizes. This allows reverse mapping. For example, it makes possible to map [-1.0, 1.0] to [3.0, 2.0], giving the following results:
convertRatio(-1.0, -1.0, 1.0, 3.0, 2.0) = 3.0
convertRatio(-0.8, -1.0, 1.0, 3.0, 2.0) = 2.9
convertRatio(0.8, -1.0, 1.0, 3.0, 2.0) = 2.1
convertRatio(1.0, -1.0, 1.0, 3.0, 2.0) = 2.0
The only condition needed is that in_min != in_max (to prevent division by zero) and out_min != out_max (otherwise, all inputs will be mapped to the same point). To prevent rounding errors, try to not use small ranges.

Try
(double) inpMaxLevel - (double) inpMinLevel
instead. What you are doing currently is subtracting max from min while the numbers are still of type int - which necessarily overflows; a signed int is fundamentally incapable of representing the difference between its min and max.

Related

Interpolation search?

I have a uniform 1D grid with value {0.1, 0.22, 0.35, 0.5, 0.78, 0.92}. These values are equally positioned from position 0 to 5 like following:
value 0.1 0.22 0.35 0.5 0.78 0.92
|_________|_________|_________|_________|_________|
position 0 1 2 3 4 5
Now I like to extract/interpolated value positioned, say, at 2.3, which should be
val(2.3) = val(2)*(3-2.3) + val(3)*(2.3-2)
= 0.35*0.7 + 0.5*0.3
= 0.3950
So how should I do it in a optimized way in C++? I am on Visual Studio 2017.
I can think of a binary search, but is any some std methods/or better way to do the job? Thanks.
You can get the integer part of the interpolation value and use that to index the two values you need to interpolate between. No need to use binary search as you are always know between which two values you interpolate. Only need to look out for indices that are outside of the values if that could ever happen.
This only works if the values are always mapped to integer indices starting with zero.
#include <cmath>
float get( const std::vector<float>& val, float p )
{
// let's assume p is always valid so it is good as index
const int a = static_cast<int>(p); // round down
const float t = p - a;
return std::lerp(val[a], val[a+1], t);
}
Edit:
std::lerp is a c++20 feature. If you use earlier versions you can use the following implementation which should be good enough:
float lerp(float a, float b, float t)
{
return a + (b - a) * t;
}

Code for normal distribution returns unexpected values [duplicate]

From this question: Random number generator which gravitates numbers to any given number in range? I did some research since I've come across such a random number generator before. All I remember was the name "Mueller", so I guess I found it, here:
Box-Mueller transform
I can find numerous implementations of it in other languages, but I can't seem to implement it correctly in C#.
This page, for instance, The Box-Muller Method for Generating Gaussian Random Numbers says that the code should look like this (this is not C#):
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
double gaussian(void)
{
static double v, fac;
static int phase = 0;
double S, Z, U1, U2, u;
if (phase)
Z = v * fac;
else
{
do
{
U1 = (double)rand() / RAND_MAX;
U2 = (double)rand() / RAND_MAX;
u = 2. * U1 - 1.;
v = 2. * U2 - 1.;
S = u * u + v * v;
} while (S >= 1);
fac = sqrt (-2. * log(S) / S);
Z = u * fac;
}
phase = 1 - phase;
return Z;
}
Now, here's my implementation of the above in C#. Note that the transform produces 2 numbers, hence the trick with the "phase" above. I simply discard the second value and return the first.
public static double NextGaussianDouble(this Random r)
{
double u, v, S;
do
{
u = 2.0 * r.NextDouble() - 1.0;
v = 2.0 * r.NextDouble() - 1.0;
S = u * u + v * v;
}
while (S >= 1.0);
double fac = Math.Sqrt(-2.0 * Math.Log(S) / S);
return u * fac;
}
My question is with the following specific scenario, where my code doesn't return a value in the range of 0-1, and I can't understand how the original code can either.
u = 0.5, v = 0.1
S becomes 0.5*0.5 + 0.1*0.1 = 0.26
fac becomes ~3.22
the return value is thus ~0.5 * 3.22 or ~1.6
That's not within 0 .. 1.
What am I doing wrong/not understanding?
If I modify my code so that instead of multiplying fac with u, I multiply by S, I get a value that ranges from 0 to 1, but it has the wrong distribution (seems to have a maximum distribution around 0.7-0.8 and then tapers off in both directions.)
Your code is fine. Your mistake is thinking that it should return values exclusively within [0, 1]. The (standard) normal distribution is a distribution with nonzero weight on the entire real line. That is, values outside of [0, 1] are possible. In fact, values within [-1, 0] are just as likely as values within [0, 1], and moreover, the complement of [0, 1] has about 66% of the weight of the normal distribution. Therefore, 66% of the time we expect a value outside of [0, 1].
Also, I think this is not the Box-Mueller transform, but is actually the Marsaglia polar method.
I am no mathematician, or statistician, but if I think about this I would not expect a Gaussian distribution to return numbers in an exact range. Given your implementation the mean is 0 and the standard deviation is 1 so I would expect values distributed on the bell curve with 0 at the center and then reducing as the numbers deviate from 0 on either side. So the sequence would definitely cover both +/- numbers.
Then since it is statistical, why would it be hard limited to -1..1 just because the std.dev is 1? There can statistically be some play on either side and still fulfill the statistical requirement.
The uniform random variate is indeed within 0..1, but the gaussian random variate (which is what Box-Muller algorithm generates) can be anywhere on the real line. See wiki/NormalDistribution for details.
I think the function returns polar coordinates. So you need both values to get correct results.
Also, Gaussian distribution is not between 0 .. 1. It can easily end up as 1000, but probability of such occurrence is extremely low.
This is a monte carlo method so you can't clamp the result, but what you can do is ignore samples.
// return random value in the range [0,1].
double gaussian_random()
{
double sigma = 1.0/8.0; // or whatever works.
while ( 1 ) {
double z = gaussian() * sigma + 0.5;
if (z >= 0.0 && z <= 1.0)
return z;
}
}

Calculate saw and triangle wave from specific data

I need to calculate a triangle and saw wave but it is a little complicate because of my model and the data I'm able to work with (but maybe I'm just confused).
I'm able to calculate my sine wave but I'm not really using a frame counter. What I do is, calculate a theta_increment variable which I can use the next time I need to calculate a sample. This works like this:
float x = note.frequency / AppSettings::sampleRate;
float theta_increment = 2.0f * M_PI * x;
float value = 0;
if(waveType == SINE){
value = sin(note.theta) * fixedAmplitude;
}
Now that I have the value of the currend frame/sample I store theta_increment inside my note.theta member so I can use it for the next sample:
note.theta += theta_increment;
I've looked at tons of examples on how I should calculate a saw or a triangle but I can't figure it out. (I only have the data mentioned above at my disposal) This is my last attempt but it's not working and giving me tons of glitches:
value = 1.0f - (2.0f * ((float)note.theta / (float)44100));
If you have a loop generating your values like this:
for (size_t frame=0; frame!=n_frames; ++frame) {
float pos = fmod(frequency*frame/sample_rate,1.0);
value[frame] = xFunc(pos)*fixedAmplitude;
}
Then you can use these functions for different types of waves:
float sinFunc(float pos)
{
return sin(pos*2*M_PI);
}
float sawFunc(float pos)
{
return pos*2-1;
}
float triangleFunc(float pos)
{
return 1-fabs(pos-0.5)*4;
}
The basic idea is that you want a value (pos) that goes from 0.0 to 1.0 over each cycle. You can then shape this however you want.
For a sine wave, the sin() function does the job, you just need to multiply by 2*PI to convert the 0.0 to 1.0 range into a 0.0 to 2*PI range.
For a sawtooth wave, you just need to convert the 0.0 to 1.0 range into a -1.0 to 1.0 range. Multiplying by two and subtracting one does that.
For a triangle wave, you can use the absolute value function to cause the sudden change in direction. First we map the 0.0 to 1.0 range into a -0.5 to 0.5 range by subtracting -0.5. Then we make this into a 0.5 to 0.0 to 0.5 shape by taking the absolute value. By multiplying by 4, we convert this into a 2.0 to 0.0 to 2.0 shape. And finally by subtracting it from one, we get a -1.0 to 1.0 to -1.0 shape.
A sawtooth wave could be calculated like this:
value = x - floor(x);
A triangle could be calculated like this:
value = 1.0 - fabs(fmod(x,2.0) - 1.0);
where x is note.theta.

Precision to drand48

How can I add precision to drand48() in C++?
I am using it in a function like:
double x = drand48()%1000+1;
to generate numbers below 1000.
But then I get this error:
error: invalid operands of types ‘double’ and ‘int’ to binary ‘operator%’
This does not happen when I use:
double x = rand()%1000+1;
Why and what is the difference between rand() and drand48()?
drand48 returns a number from the interval [0.0, 1.0). Are you looking for a number between 1 and 1000? In this case, you need to multiply by 999 and add 1.
Actually, what are you expecting?
drand48() returns a double, whereas rand() returns int.
Furthermore, drand48() returns a value that's distributed between [0.0, 1.0), so your formula needs to change:
double x = drand48() * 1000.0 + 1; // floating-point values from [1, 1001)
or
double x = (int)(drand48() * 1000.0) + 1; // integer values from [1, 1000]
You could either scale the result of drand48() as above, or use lrand48() with your existing formula.
drand48 returns a double in the range of 0.0 to 1.0. You want to multiply that by the range you're looking to generate. double x = drand48() * 1000.0

C++ 'map' number ranges

Is there a better way in C or C++, or just mathematically in general, to map ratios of numbers while preserving or rounding data?
Take the following example
double cdp = 17000.0;
float integ = 30000.0 / 255;
int val = cdp / integ;
color = color + RGB(val, val, val);
Here I want to map a range of numbers [0, 30000] to a value [0, 255]
You can use a simple linear interpolation to do it, if I'm understanding correctly. It would work like this:
x = (inValue - minInRange) / (maxInRange - minInRange);
result = minOutRange + (maxOutRange - minOutRange) * x
where inValue is the number out of 30,000 in your example. minInRange = 0, maxInRange = 30,000, minOutRange = 0, maxOutRange = 255.
Multiply by 255 then divide by 30000. Use an integer format that can hold the product of your two range limits, 30000*255 or 7.65 million. This avoids the precision issues with intermediate floating point values.
If you want to round to the nearest value rather than truncate any fractional component, then you have to do this:
double prod = cpd * 255; //double is big enough to hold the product without loss of precision
val = (int)(prod / 30000 + 0.5); //Adding 0.5 turns truncation into rounding.
In case someone needs templated version of #user1118321 proposed
template<typename T>
struct interpolation{
T min_in, max_in, min_out, max_out;
interpolation(T min_in_, T max_in_, T min_out_, T max_out_): min_in(min_in_), max_in(max_in_), min_out(min_out_), max_out(max_out_){
}
};
template<typename T, typename interpolate_type>
T map_range_1d(T value, interpolate_type interp_type){
double x = (value - interp_type.min_in) / (interp_type.max_in - interp_type.min_in);
return interp_type.min_out + (interp_type.max_out - interp_type.min_out) * x;
}
template<typename T, typename interpolate_type>
std::pair<T,T> map_range_2d(T value_x, T value_y, interpolate_type interp_type_x, interpolate_type interp_type_y){
std::pair<T,T> mapped_values = {map_range_1d(value_x, interp_type_x), map_range_1d(value_y, interp_type_y)};
return mapped_values;
}
typedef interpolation<double> interp1d;
constexpr auto map_range2d = &map_range_2d<double, interp1d>;
constexpr auto map_range1d = &map_range_1d<double, interp1d>;
How to use it
interp1d x_range(min_x_in, max_x_in, min_x_out, max_x_out);
double index_local = map_range1d(456, x_range);