I’m a learner and, as an exercise, I’m trying to write some code to work with finite and infinite continued fractions. I have some code that is OK and seems to be correct: it emits a digit only when it’s certain, and processes another term from the list of coefficients when it needs to. I’m using the mathematics from: https://cdsmithus.medium.com/continued-fractions-haskell-equational-reasoning-property-testing-and-rewrite-rules-in-action-77a16d750e3f
I would like my code to be more elegant. At the moment, I have a special function to “drain” all remaining digits (maybe a finite number, maybe an infinitely repeating decimal) when the coefficients run out. But I feel like it must be possible to have a single emitting function that does both jobs.
matmul (a1, b1, c1, d1) (a2, b2, c2, d2) = (a1*a2 + b1*c2,
a1*b2 + b1*d2,
c1*a2 + d1*c2,
c1*b2 + d1*d2)
cf_to_decimal :: [Integer] -> [Integer]
cf_to_decimal cf = go (1, 0, 0, 1) cf
where
emit :: (Integer, Integer, Integer, Integer) -> Maybe Integer
emit (a,b,c,d)
| c /= 0 && d /= 0 && y0 == y1 = Just y0
| otherwise = Nothing
where
y0 = fromIntegral (a `div` c)
y1 = fromIntegral (b `div` d)
emitAll :: (Integer, Integer, Integer, Integer) -> [Integer]
emitAll (a,b,c,d)
| a == 0 = []
| c /= 0 && d /= 0 && y0 == y1 = y0:go (matmul (10, -10*y0, 0, 1) (a,b,c,d)) []
where
y0 = fromIntegral (a `div` c)
y1 = fromIntegral (b `div` d)
go :: (Integer, Integer, Integer, Integer) -> [Integer] -> [Integer]
go (a,b,c,d) (x:xs) =
case emit (a,b,c,d) of
Just d' -> d':go (matmul (10, -10*d', 0, 1) (a,b,c,d)) (x:xs)
Nothing -> go (matmul (a,b,c,d) (x,1,1,0)) xs
go (a,_,c,_) [] = emitAll (a,a,c,c)
A few experiments seem to have run into laziness problems? Here’s one attempt, inspired by unfoldr, that’s probably a bit of a mess:
matmul (a1, b1, c1, d1) (a2, b2, c2, d2) = (a1*a2 + b1*c2,
a1*b2 + b1*d2,
c1*a2 + d1*c2,
c1*b2 + d1*d2)
cf_to_decimal cf = go (1, 0, 0, 1) cf
where
emit (digits, (a,b,c,d))
| a == 0 = (digits, (a,b,c,d))
| c /= 0 && d /= 0 && y0 == y1 = emit (digits ++ [y0], (matmul (10, -10*y0, 0, 1) (a,b,c,d)))
| otherwise = (digits, (a,b,c,d))
where
y0 = fromIntegral (a `div` c)
y1 = fromIntegral (b `div` d)
go :: (Integer, Integer, Integer, Integer) -> [Integer] -> [Integer]
go (0,_,_,_) _ = []
go (a,b,c,d) (x:xs) = case emit ([], (a,b,c,d)) of
([], m) -> go (matmul m (x, 1, 1, 0)) xs
(d, m') -> d ++ go m' (x:xs)
go (a,_,c,_) [] = case emit ([], (a,a,c,c)) of
([], _) -> []
(d, m') -> d
Thanks for any comments.