digit-0.5.2: 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.Digit
import Data.Digit.Integral (integralDecimal)
import Numeric.Natural (Natural)
import Data.Scientific (toDecimalDigits)
-- |
--
-- >>> _NaturalDigits # 0
-- 0 :| []
--
-- >>> _NaturalDigits # 1
-- 1 :| []
--
-- >>> _NaturalDigits # 9223372036854775807
-- 9 :| [2,2,3,3,7,2,0,3,6,8,5,4,7,7,5,8,0,7]
--
-- >>> (9 :| [2,2,3,3,7,2,0,3,6,8,5,4,7,7,5,8,0,7]) ^? _NaturalDigits
-- Just 9223372036854775807
--
-- >>> (1 :| []) ^? _NaturalDigits
-- Just 1
--
-- prop> \x -> digitsToNatural ( naturalToDigits x ) == Just x
--
_NaturalDigits :: Prism' (NonEmpty Digit) Natural
_NaturalDigits = prism' naturalToDigits digitsToNatural
-- | NonEmpty Digits from a Natural number
--
-- >>> naturalDigits 0
-- 0 :| []
--
-- >>> naturalDigits 9
-- 9 :| []
--
-- >>> naturalDigits 393564
-- 3 :| [9,3,5,6,4]
--
-- >>> naturalDigits 9223372036854775807
-- 9 :| [2,2,3,3,7,2,0,3,6,8,5,4,7,7,5,8,0,7]
--
naturalToDigits :: Natural -> NonEmpty Digit
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) Digit0
-- EWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW!
-- But you can't reach this point unless you have a non-zero absolute integral value. So... I dunno.
g 0 = Digit0
g 1 = Digit1
g 2 = Digit2
g 3 = Digit3
g 4 = Digit4
g 5 = Digit5
g 6 = Digit6
g 7 = Digit7
g 8 = Digit8
g 9 = Digit9
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.
--
-- >>> naturalFromDigits (D.x3 :| [D.x4])
-- Just 34
--
-- >>> naturalFromDigits (D.Digit3 :| [D.Digit9,D.Digit3,D.Digit5,D.Digit6,D.Digit4])
-- Just 393564
--
-- >>> naturalFromDigits (D.x0 :| [])
-- Just 0
--
-- Int maxBound for Int64
-- >>> naturalFromDigits (D.x9 :| [D.x2,D.x2,D.x3,D.x3,D.x7,D.x2,D.x0,D.x3,D.x6,D.x8,D.x5,D.x4,D.x7,D.x7,D.x5,D.x8,D.x0,D.x7])
-- Just 9223372036854775807
--
-- Int maxBound + 1 for Int64
-- >>> naturalFromDigits (D.x9 :| [D.x2,D.x2,D.x3,D.x3,D.x7,D.x2,D.x0,D.x3,D.x6,D.x8,D.x5,D.x4,D.x7,D.x7,D.x5,D.x8,D.x0,D.x8])
-- Nothing
--
digitsToNatural :: NonEmpty Digit -> Maybe Natural
digitsToNatural = fmap fromIntegral . ifoldrM f 0 . NE.reverse
where
f :: Int -> Digit -> Int -> Maybe Int
f i d curr =
let
next = (integralDecimal # d) * (10 ^ i)
in
if curr > maxBound - next
then Nothing
else Just (curr + next)