2014-11-10 27 views
7

Sto provando a calcolare la lunghezza di un intero in Haskell, usando il fatto che la lunghezza è uguale a truncate (log10(x)+1).Errore logbase di Haskell

Uso interi ho creato:

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

Purtroppo, non tutti i numeri di ottenere la lunghezza corretta. Ho provato un paio di casi diversi e ha scoperto che:

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 

C'è un motivo per cui logBase 10 1000 non restituisce 3.0? Come ottengo il valore log corretto per 1000 in base 10?

+0

'logBase' è definita come' log y/log x'; la divisione è probabilmente il colpevole, poiché mentre il 'log' sarà corretto (per quanto riguarda gli arrotondamenti), la divisione di essi non deve essere. –

+0

@BartekBanachewicz Quindi non c'è modo di ottenere il risultato corretto? Provato usando (log 1000)/(log 10), ma ancora 2.99996. Indovina che i valori di registro potrebbero essere arrotondati per 1000 e fino a 10, quindi sarà leggermente più piccolo di 3. – Pphoenix

+1

Supponiamo che 'log' non sia necessario, potresti evitare l'operazione in virgola mobile dividendo ripetutamente per 10 (e più alta potenza di 10). – kennytm

risposta

4

Esiste una funzione di registro dei numeri interi nei moduli GHC che ha il tipo Integer -> Integer -> Int#.

Esempio utilizzo:

{-# 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 

uscita:

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 
1

Se non avete bisogno di Double precisione Floating quindi utilizzare Float tipo, invece, e sembra che vada bene. Ad esempio logBase 10 (1000 :: Float) restituirebbe 3.0 o funzionalmente farebbe lo stesso.

Come per il codice toInteger sembra ridondante dal truncate :: (Integral b, RealFrac a) => a -> b già fa quel lavoro. Quindi può semplicemente fare come

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

Questo funziona correttamente fino 9999987.