The easy part is converting from millimeters to inches – just divide by 25.4. The hard part is finding the nearest 32nd (or 16th, 64th, etc.).
At the end of this answer, I include a function for doing so called toMixedNumber(). It follows the following algorithm:
- Multiply the floating-point value by the desired denominator, rounding to the nearest unit.
- Divide by the denominator. The quotient is the integer part, and the remainder is the numerator.
- Optionally, reduce the fractional part to lowest terms by dividing both the numerator and the denominator by the greatest common divisor (GCD). The GCD can be calculated using Euclid's algorithm.
- Format the integer and fractional parts as a string in "i", "n/d", or "i n/d" format as appropriate. In the "i n/d" case, remove any minus sign in the fractional part.
Using the function is straightforward:
const MM_PER_IN = 25.4;
echo toMixedNumber(14 / MM_PER_IN, 32) . "\n"; // 9/16
echo toMixedNumber(55 / MM_PER_IN, 32) . "\n"; // 2 5/32
echo toMixedNumber(321 / MM_PER_IN, 32) . "\n"; // 12 5/8
echo toMixedNumber(321 / MM_PER_IN, 64) . "\n"; // 12 41/64
Interestingly, if the denominator is always a power of two (as in your case), an optimization becomes possible. For a proper dyadic fraction with a positive numerator, the value of the least significant "1" bit in the numerator is the GCD of the numerator and denominator:
if ($reduce) {
$gcd = $num & -$num;
$num /= $gcd;
$denom /= $gcd;
}
I adapted the bit twiddling trick from Innovative way for checking if number has only one on bit in signed int, and on my computer, it speeds up the function by about 20%. (It works because the value of any bit is a divisor of all values of more significant bits, and the denominator's sole "1" bit is more significant than any in the numerator.) However, I chose not to include it.
function toMixedNumber($arg, $denom, $reduce = true) {
$num = round($arg * $denom);
$int = (int)($num / $denom);
$num %= $denom;
if (!$num) {
return "$int";
}
if ($reduce) {
// Use Euclid's algorithm to find the GCD.
$a = $num < 0 ? -$num : $num;
$b = $denom;
while ($b) {
$t = $b;
$b = $a % $t;
$a = $t;
}
$num /= $a;
$denom /= $a;
}
if ($int) {
// Suppress minus sign in numerator; keep it only in the integer part.
if ($num < 0) {
$num *= -1;
}
return "$int $num/$denom";
}
return "$num/$denom";
}
License for the above example code