In today’s Programming Praxis exercise, our goal is to convert a decimal length value to the fractions used by carpenters. Let’s get started, shall we?

Some imports:

import Data.Ratio
import Text.Printf

To get proper rounding we first multiply by 32 and then see how many feet, inches and fractions of an inch there are.

toCarpenter :: RealFrac a => a -> (Int, Int, Ratio Int)
toCarpenter l = (feet, div r 32, mod r 32 % 32) where
(feet, r) = divMod (round $ l * 32) (32 * 12)

Formatting the text is a unfortunately a tad unwieldy due to the number of special cases.

feetAndInches :: RealFrac a => a -> String
feetAndInches l = case toCarpenter l of
(0,0,0) -> "0 feet 0 inches"
(f,i,t) -> showUnit "foot" "feet" (f % 1) ++
(if f > 0 && (i%1 + t) > 0 then " " else "") ++
showUnit "inch" "inches" (i % 1 + t)
where
showUnit _ _ 0 = ""
showUnit s m n = printf "%s %s" (showVal n) $ if n <= 1 then s else m
showVal v | d == 1 = show n
| v < 1 = printf "%d/%d" n d
| otherwise = printf "%d and %d/%d" (div n d) (mod n d) d
where (n,d) = (numerator v, denominator v)

Some tests to see if everything is working properly:

main :: IO ()
main = do print $ feetAndInches 0 == "0 feet 0 inches"
print $ feetAndInches 0.2785 == "9/32 inch"
print $ feetAndInches 1.6895 == "1 and 11/16 inches"
print $ feetAndInches 11.9999 == "1 foot"
print $ feetAndInches 12.2785 == "1 foot 9/32 inch"
print $ feetAndInches 71.9999 == "6 feet"
print $ feetAndInches 72 == "6 feet"
print $ feetAndInches 72.3492 == "6 feet 11/32 inch"
print $ feetAndInches 72.9999 == "6 feet 1 inch"
print $ feetAndInches 73 == "6 feet 1 inch"
print $ feetAndInches 73.0135 == "6 feet 1 inch"
print $ feetAndInches 73.0185 == "6 feet 1 and 1/32 inches"
print $ feetAndInches 73.8218 == "6 feet 1 and 13/16 inches"