lecho.lib.hellocharts.util.FloatUtils Maven / Gradle / Ivy
package lecho.lib.hellocharts.util;
public class FloatUtils {
public static final int POW10[] = {1, 10, 100, 1000, 10000, 100000, 1000000};
/**
* Returns next bigger float value considering precision of the argument.
*/
public static float nextUpF(float f) {
if (Float.isNaN(f) || f == Float.POSITIVE_INFINITY) {
return f;
} else {
f += 0.0f;
return Float.intBitsToFloat(Float.floatToRawIntBits(f) + ((f >= 0.0f) ? +1 : -1));
}
}
/**
* Returns next smaller float value considering precision of the argument.
*/
public static float nextDownF(float f) {
if (Float.isNaN(f) || f == Float.NEGATIVE_INFINITY) {
return f;
} else {
if (f == 0.0f) {
return -Float.MIN_VALUE;
} else {
return Float.intBitsToFloat(Float.floatToRawIntBits(f) + ((f > 0.0f) ? -1 : +1));
}
}
}
/**
* Returns next bigger double value considering precision of the argument.
*/
public static double nextUp(double d) {
if (Double.isNaN(d) || d == Double.POSITIVE_INFINITY) {
return d;
} else {
d += 0.0;
return Double.longBitsToDouble(Double.doubleToRawLongBits(d) + ((d >= 0.0) ? +1 : -1));
}
}
/**
* Returns next smaller float value considering precision of the argument.
*/
public static double nextDown(double d) {
if (Double.isNaN(d) || d == Double.NEGATIVE_INFINITY) {
return d;
} else {
if (d == 0.0f) {
return -Float.MIN_VALUE;
} else {
return Double.longBitsToDouble(Double.doubleToRawLongBits(d) + ((d > 0.0f) ? -1 : +1));
}
}
}
/**
* Method checks if two float numbers are similar.
*/
public static boolean almostEqual(float a, float b, float absoluteDiff, float relativeDiff) {
float diff = Math.abs(a - b);
if (diff <= absoluteDiff) {
return true;
}
a = Math.abs(a);
b = Math.abs(b);
float largest = (a > b) ? a : b;
if (diff <= largest * relativeDiff) {
return true;
}
return false;
}
/**
* Rounds the given number to the given number of significant digits. Based on an answer on Stack Overflow.
*/
public static float roundToOneSignificantFigure(double num) {
final float d = (float) Math.ceil((float) Math.log10(num < 0 ? -num : num));
final int power = 1 - (int) d;
final float magnitude = (float) Math.pow(10, power);
final long shifted = Math.round(num * magnitude);
return shifted / magnitude;
}
/**
* Formats a float value to the given number of decimals. Returns the length of the string. The string begins at
* [endIndex] - [return value] and ends at [endIndex]. It's up to you to check indexes correctness.
* Parameter [endIndex] can be helpful when you want to append some text to formatted value.
*
* @return number of characters of formatted value
*/
public static int formatFloat(final char[] formattedValue, float value, int endIndex, int digits, char separator) {
if (digits >= POW10.length) {
formattedValue[endIndex - 1] = '.';
return 1;
}
boolean negative = false;
if (value == 0) {
formattedValue[endIndex - 1] = '0';
return 1;
}
if (value < 0) {
negative = true;
value = -value;
}
if (digits > POW10.length) {
digits = POW10.length - 1;
}
value *= POW10[digits];
long lval = Math.round(value);
int index = endIndex - 1;
int charsNumber = 0;
while (lval != 0 || charsNumber < (digits + 1)) {
int digit = (int) (lval % 10);
lval = lval / 10;
formattedValue[index--] = (char) (digit + '0');
charsNumber++;
if (charsNumber == digits) {
formattedValue[index--] = separator;
charsNumber++;
}
}
if (formattedValue[index + 1] == separator) {
formattedValue[index--] = '0';
charsNumber++;
}
if (negative) {
formattedValue[index--] = '-';
charsNumber++;
}
return charsNumber;
}
/**
* Computes the set of axis labels to show given start and stop boundaries and an ideal number of stops between
* these boundaries.
*
* @param start The minimum extreme (e.g. the left edge) for the axis.
* @param stop The maximum extreme (e.g. the right edge) for the axis.
* @param steps The ideal number of stops to create. This should be based on available screen space; the more
* space
* there is, the more stops should be shown.
* @param outValues The destination {@link AxisAutoValues} object to populate.
*/
public static void computeAutoGeneratedAxisValues(float start, float stop, int steps, AxisAutoValues outValues) {
double range = stop - start;
if (steps == 0 || range <= 0) {
outValues.values = new float[]{};
outValues.valuesNumber = 0;
return;
}
double rawInterval = range / steps;
double interval = roundToOneSignificantFigure(rawInterval);
double intervalMagnitude = Math.pow(10, (int) Math.log10(interval));
int intervalSigDigit = (int) (interval / intervalMagnitude);
if (intervalSigDigit > 5) {
// Use one order of magnitude higher, to avoid intervals like 0.9 or 90
interval = Math.floor(10 * intervalMagnitude);
}
double first = Math.ceil(start / interval) * interval;
double last = nextUp(Math.floor(stop / interval) * interval);
double intervalValue;
int valueIndex;
int valuesNum = 0;
for (intervalValue = first; intervalValue <= last; intervalValue += interval) {
++valuesNum;
}
outValues.valuesNumber = valuesNum;
if (outValues.values.length < valuesNum) {
// Ensure stops contains at least numStops elements.
outValues.values = new float[valuesNum];
}
for (intervalValue = first, valueIndex = 0; valueIndex < valuesNum; intervalValue += interval, ++valueIndex) {
outValues.values[valueIndex] = (float) intervalValue;
}
if (interval < 1) {
outValues.decimals = (int) Math.ceil(-Math.log10(interval));
} else {
outValues.decimals = 0;
}
}
}