Haskell logbase error
Asked Answered
C

4

10

I am trying to calculate the length of an Integer in Haskell, using the fact that the length is equal to truncate (log10(x)+1).

Using Integers I created:

len :: Integer -> Integer
len i = toInteger (truncate (logBase 10 (fromIntegral i)) + 1)

Unfortunately, not all numbers get the correct length. I tried a few different cases and found that:

logBase 10 10         = 1.0
logBase 10 100        = 2.0
logBase 10 1000       = 2.9999..6
logBase 10 10000      = 4.0
logBase 10 100000     = 5.0
logBase 10 1000000    = 5.9999999

Is there a reason why logBase 10 1000 doesn't return 3.0? How do I get the correct log-value for 1000 in base 10?

Columbous answered 10/11, 2014 at 12:43 Comment(5)
logBase is defined as log y / log x; the division is probably the culprit, as while the log will be correct (with regard to roundings), the division of them doesn't have to be.Osteogenesis
@BartekBanachewicz So there is no way to get the correct result? Tried using (log 1000) / (log 10), but still 2.99996. Guess that the log-values might be rounded down for 1000 and up for 10, thus it will be slightly smaller than 3.Columbous
Suppose log is not necessary, you could avoid floating point operation with repeatedly divide by 10 (and higher power of 10).Hittite
@Columbous The easiest way to get the correct result could be to use numbers package, and call logBase 10 1000 :: BigFloat Prec50, then round that into float or double. Easiest, but not necessarily the best.Osteogenesis
If you don't need Double Floating precision then use Float type instead and it seems fine. Such as logBase 10 (1000 :: Float) would return 3.0 or functionally logBase 10 . (fromInteger :: Integer -> Float) $ 1000 would do the same.Layton
C
5

There is an integer log base function in GHC modules which has type Integer -> Integer -> Int#.

Example usage:

{-# LANGUAGE MagicHash #-}

import Control.Monad
import GHC.Integer.Logarithms ( integerLogBase# )
import GHC.Exts (Int(..))

main = do
  forM_ [(1::Int)..20] $ \n -> do
    let a = 10^n-1
        la = I# (integerLogBase# 10 a)
        b = 10^n
        lb = I# (integerLogBase# 10 b)
    putStrLn $ show a ++ " -> " ++ show la
    putStrLn $ show b ++ " -> " ++ show lb

Output:

9 -> 0
10 -> 1
99 -> 1
100 -> 2
999 -> 2
1000 -> 3
9999 -> 3
10000 -> 4
99999 -> 4
100000 -> 5
999999 -> 5
1000000 -> 6
9999999 -> 6
...
9999999999999999999 -> 18
10000000000000000000 -> 19
99999999999999999999 -> 19
100000000000000000000 -> 20
Chocolate answered 10/11, 2014 at 15:50 Comment(0)
L
1

If you don't need Double Floating precision then use Float type instead and it seems fine. Such as logBase 10 (1000 :: Float) would return 3.0 or functionally logBase 10 . (fromInteger :: Integer -> Float) $ 1000 would do the same.

As per your code toInteger seems redundant since truncate :: (Integral b, RealFrac a) => a -> b already does that job. So may simply do like

len :: Integer -> Integer
len = (+1) . truncate . logBase 10 . (fromIntegral :: Integer -> Float)

This will work correctly up until 9999987.

Layton answered 14/11, 2017 at 23:25 Comment(0)
D
1

Can't you just go

amountOfDigits = length . show

(if you only interested in amount of digits in traditional base 10)

Demetriusdemeyer answered 11/4, 2019 at 13:2 Comment(1)
This would fail for numbers with leading zeros.Englishism
E
-1
len :: Integer -> Integer
len x
  | x <= 1 = 1 -- doesn't handle negative numbers
  | otherwise = ceiling (logBase 10 (fromIntegral x :: Float))
Englishism answered 24/12, 2022 at 7:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.