Page 1 of 1

Faster (but less accurate) Hz to volts and volts to dB conversions

Posted: Sat Apr 13, 2024 8:52 pm
by honki-bobo
Dear fellow VM module developers,

as most of you know, the Values class offers some static methods for faster calculations of the most commonly used math functions.

While there are fast implementations of the power function for the bases e, 2 and 10 (Fast[E|Ten|Two]ToTheX), which are somewhere between 2 to 6 times faster than Javas Math.pow function (depending on the input values), the Values class is missing a fast implementation of the logarithm.

Yes, there is the PercentToDecibels method, which can be "abused" to calculate a logarithm, but it isn't really faster than Javas Math.log function (actually it was slower in the tests I did). So I went into the deepest depths of the interwebs on the hunt for a fast logarithm implementation and I came across this. It's implemented in C, but I was up for the challenge to make this usable in Java for module development. So, long story short, this is what I came up with:

Code: Select all

public static float fastLog2(float x) {
	if (x < 0.0f) {
		return Float.NaN;
	} else if (x == 0.0f) {
		return Float.NEGATIVE_INFINITY;
	}
	int i = Float.floatToRawIntBits(x);
	if ((i & 0x007FFFFF) == 0x0) { // mantissa is 0, so return unbiased exponent
		return ((i >> 23) & 0xFF) - 127.0f;
	}
	float f = Float.intBitsToFloat((i & 0x007FFFFF) | 0x3F000000);
	return i * 1.1920928955078125e-7f - 124.22551499f
			- 1.498030302f * f
			- 1.72587999f / (0.3520887068f + f);
}

public static float fasterLog2(float x) {
	if (x < 0.0f) {
		return Float.NaN;
	} else if (x == 0.0f) {
		return Float.NEGATIVE_INFINITY;
	}
	int i = Float.floatToRawIntBits(x);
	if ((i & 0x007FFFFF) == 0x0) { // mantissa is 0, so return unbiased exponent
		return ((i >> 23) & 0xFF) - 127.0f;
	}
	return i * 1.1920928955078125e-7f - 126.94269504f;
}

// for convenience
public static double fastLog2(double x) {
	return (double)fastLog2((float)x);
}

public static double fasterLog2(double x) {
	return (double)fasterLog2((float)x);
}
The fastLog2 is accurate to around the 5th digit and up to 2.2 times faster than Math.log, while fasterLog2 is up to 5 times faster, but only accurate to the 2nd digit. I found that I could increase accuracy for actual powers of 2 by utilizing the IEEE 754 floating point format. For powers of 2 the mantissa of the floating point number is all 0s. In that case the unbiased exponent is returned.

You can use the fastLog2 to convert Hz to volts (-0.25V = a0 = 55.0 Hz) with

Code: Select all

public static double HzToVolts(double hz) {
	return fastLog2(hz / 55.0) - 0.25;
}
or to get decibels from a percentage:

Code: Select all

public static final double TWENTY_OVER_LOG2_TEN = 6.0205999132796239042747778944898605353637;

public static double PercentToDecibels(double percent) {
	if (percent <= 0.0) {
		return Double.NEGATIVE_INFINITY;
	} else if (percent == 1.0) {
		return 0.0;
	} 
	return TWENTY_OVER_LOG2_TEN * fastLog2(percent);
}
In my tests I found that Values.PercentToDecibels actually takes 25% longer than the direct implementation using 20.0 * Math.log10(percent), while the above implementation is a bit more than twice as fast, though less accurate.

If anyone knows of a faster and/or more accurate implementation, please feel free to share it here.

Best regards,
Martin

Re: Faster (but less accurate) Hz to volts and volts to dB conversions

Posted: Sat Apr 13, 2024 9:34 pm
by UrbanCyborg
I kinda suspect that most of the nifty tricks depend on precomputing key pieces for known special cases of bases, powers, etc. If you're really a shark with logs and exponentials, you can get some passable speed-ups for special cases that are important to your specific code, but that sort of thing doesn't extend well to any kind of general solution. If you had lots of space, table look-ups would work well (face it, that's about how most computing was done in pre-electronic days; the situation Heinlein posits at the Naval Academy in "Time Enough for Love" comes to mind).

Reid