digit-0.15: src/Data/Digit/Natural.hs
module Data.Digit.Natural
( _NaturalDigits
, naturalToDigits
, digitsToNatural
) where
import Prelude (Int, error, fromIntegral, maxBound, (*),
(+), (-), (>), (^))
import Control.Category ((.))
import Control.Lens (Prism', ifoldrM, prism', ( # ))
import Data.Foldable (length)
import Data.Function (($))
import Data.Functor (fmap, (<$>))
import Data.Semigroup ((<>))
import Data.List (replicate)
import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.List.NonEmpty as NE
import Data.Maybe (Maybe (..))
import Data.Digit.Decimal
import Data.Digit.Integral (integralDecimal)
import Numeric.Natural (Natural)
import Data.Scientific (toDecimalDigits)
-- |
--
-- >>> _NaturalDigits # 0
-- DecDigit0 :| []
--
-- >>> _NaturalDigits # 1
-- DecDigit1 :| []
--
-- >>> _NaturalDigits # 922
-- DecDigit9 :| [DecDigit2,DecDigit2]
--
-- >>> (DecDigit9 :| [DecDigit2,DecDigit2]) ^? _NaturalDigits
-- Just 922
--
-- >>> (DecDigit1 :| []) ^? _NaturalDigits
-- Just 1
--
-- prop> \x -> digitsToNatural ( naturalToDigits x ) == Just x
--
_NaturalDigits :: Prism' (NonEmpty DecDigit) Natural
_NaturalDigits = prism' naturalToDigits digitsToNatural
-- |
--
-- >>> naturalDigits 0
-- DecDigit0 :| []
--
-- >>> naturalDigits 9
-- DecDigit9 :| []
--
-- >>> naturalDigits 393
-- DecDigit3 :| [DecDigit9,DecDigit3]
--
naturalToDigits :: Natural -> NonEmpty DecDigit
naturalToDigits n =
case toDecimalDigits $ fromIntegral n of
-- toDecimalDigits :: n -> ([n],n)
-- toDecimalDigits 0 = ([0],0)
-- toDecimalDigits (-0) = ([0],0)
-- toDecimalDigits (-1) = ([-1],1)
([], _ ) -> error "Data.Scientific.toDecimalDigits is no longer correct!"
(x:xs, eXP) -> g x :| (g <$> xs) <> t (x:xs) eXP
where
t allDigs eXP =
replicate (eXP - length allDigs) (d0 # ())
-- EWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW!
-- But you can't reach this point unless you have a non-zero absolute integral value. So... I dunno.
g 0 = d0 # ()
g 1 = d1 # ()
g 2 = d2 # ()
g 3 = d3 # ()
g 4 = d4 # ()
g 5 = d5 # ()
g 6 = d6 # ()
g 7 = d7 # ()
g 8 = d8 # ()
g 9 = d9 # ()
g _ = error "The universe now has more than ten digits."
-- | Create a number from a list of digits with the integer bounds of the machine.
--
-- >>> digitsToNatural (DecDigit3 :| [DecDigit4])
-- Just 34
--
-- >>> digitsToNatural (DecDigit0 :| [])
-- Just 0
--
-- >>> digitsToNatural (naturalToDigits (maxBound :: Natural))
-- Just 9223372036854775807
--
-- >>> digitsToNatural (naturalToDigits $ (maxBound :: Natural) + 1)
-- Nothing
digitsToNatural :: NonEmpty DecDigit -> Maybe Natural
digitsToNatural = fmap fromIntegral . ifoldrM f 0 . NE.reverse
where
f :: Int -> DecDigit -> Int -> Maybe Int
f i d curr =
let
next = (integralDecimal # d) * (10 ^ i)
in
if curr > maxBound - next
then Nothing
else Just (curr + next)