2010-08-18 3 views
0

Я пытаюсь создать небольшой модуль для выполнения вычислений на основе десятичных чисел. Ряд хранится в виде целого числа mantisse, с точностью значения, указанного в Int:Как оптимизировать этот фрагмент haskell

data APNum = 
    { getMantisse :: Integer 
    , getPrecision :: Int } 

Например:

APNum 123 0 -> 123 
APNum 123 1 -> 1.23 
APNum 123 2 -> 12.3 
... 

(отрицательная точность не допускается).

Теперь я написал эту функцию, которая автоматически регулирует точность отгонкой как многие конечные нули как можно:

autoPrecision :: APNum -> APNum 
    autoPrecision [email protected](APNum m p) = if p > maxPrecision 
    then autoPrecision $ setPrecision x maxPrecision 
    else autoPrecision' m p where 
    autoPrecision' m p = let (m',r) = m `divMod` 10 in 
     if r /= 0 || p <= 0 then APNum m p else autoPrecision' m' (pred p) 

(MaxPrecision и setPrecision очевидны, я думаю).

Проблема в том, что этот фрагмент имеет очень плохую производительность, особенно n номеров с более чем 10000 цифрами. Существуют ли какие-либо простые оптимизации?

+0

Под «ведущими нулями» вы имеете в виду «завершающие нули»? (т. е. 'APNum 12000 5' ->' APNum 12 2') – kennytm

+0

@KennyTM это то, что я предположил, поскольку Integer не может иметь ведущих нулей –

+0

Извините ... Исправлено. – fuz

ответ

3

Вы можете использовать бинарный поиск, чтобы найти максимальную мощность 10, которая делит m, вместо того чтобы пытаться использовать все последовательные значения.

import Numeric.Search.Range 
import Data.Maybe 

data APNum = APNum{getMantisse :: Integer, getPrecission :: Int} deriving Show 

setPrecision (APNum m _) x = APNum m x 
maxPrecission = 200000 

findDiv x = pred $ fromJust $ searchFromTo (p x) 0 maxPrecission where 
    p x n = x `mod` 10^n /= 0 

autoPrecision :: APNum -> APNum 
autoPrecision [email protected](APNum m p) 
= if p > maxPrecission then 
    autoPrecision $ setPrecision x maxPrecission else APNum m' p' 
where d = min (findDiv m) p 
     p' = p - d 
     m' = m `div` 10^d 

Я использую binary-search пакет здесь, который обеспечивает searchFromTo :: Integral a => (a -> Bool) -> a -> a -> Maybe a. Это должно дать вам большое ускорение.

+0

BTW ... Есть ли какая-то лень, чтобы сделать это быстрее? – fuz

+0

Это работает как ожидалось и сокращает время выполнения. Реальная проблема заключается в том, что haskell потребляет большое количество пространства при выполнении этого. Я скоро напишу полный программный пример. – fuz

0

также х mod 10 == 0 влечет х mod 2 == 0, и что дешевле, чтобы проверить на

+0

Значит, все числа делятся на 2 делится на 10, тоже? Подумайте еще раз :-) – Landei

+0

Нет, но вы можете сначала разделить на две (очень дешевые), а затем, если n = 0 mod 2 разделите на 5. – fuz

2

Похоже, даже простой строковой операции еще быстрее:

maxPrecision = 2000000 

autoPrecision (APNum m p) = 
    let p' = min p maxPrecision 
     (n',ds) = genericDropNWhile (=='0') p' $ reverse $ show m 
    in APNum (read $ reverse ds) n' 
    where 
    genericDropNWhile p n (x:xs) | n > 0 && p x = genericDropNWhile p (n-1) xs 
    genericDropNWhile _ n xs = (n,xs) 

Тест с этим :

main = print $ autoPrecision $ APNum (10^100000) (100000-3) 

EDIT: Упс, быстрее только для чисел с большим количеством нулей. В противном случае это двойное преобразование определенно медленнее.

+0

Я не уверен, что стоимость шоу и чтения будет стоить здесь , Они оба должны выполнить множество математических операций для преобразования в/из строки. –

+0

Ну, на моей машине моя «главная» функция (см. Выше) работает быстрее, чем два других варианта autoPrecision (0.05s против 0.84s и 4.05s соответственно). Но, как я заметил, это так, только когда мантисса содержит множество конечных нулей. Я думаю, это связано с тем, что повторное применение 'div' на большом Integer довольно дорого, а' show' я считаю дешевле здесь (нужно проверить реализацию 'show'). Я не говорю, что нужно использовать шоу для такого рода вещей, а скорее, что наивный 'div', вероятно, не самый оптимальный вариант здесь. –

Смежные вопросы