2013-09-10 3 views
7

Я пытаюсь сделать функцию для округления числа с плавающей запятой до определенной длины цифр. То, что я придумал до сих пор это:Округление до определенного количества цифр в Haskell

import Numeric; 

digs :: Integral x => x -> [x] <br> 
digs 0 = [] <br> 
digs x = digs (x `div` 10) ++ [x `mod` 10] 

roundTo x t = let d = length $ digs $ round x <br> 
        roundToMachine x t = (fromInteger $ round $ x * 10^^t) * 10^^(-t) 
       in roundToMachine x (t - d) 

Я использую функцию digs, чтобы определить количество цифр до запятой, чтобы оптимизировать входное значение (то есть переместить все мимо запятой, так 1.234 становится 0.1234 * 10^1)

Функция roundTo работает для большинства входных сигналов, однако для некоторых входов я получаю странные результаты, например roundTo 1.0014 4 производит 1.0010000000000001 вместо 1.001.

Проблема в этом примере вызывается путем вычисления 1001 * 1.0e-3 (который возвращает 1.0010000000000001)

Является ли это просто проблема в представлении чисел в Haskell, я должен жить с или есть лучший способ обогнуть с плавающей точкой число до определенной длины цифр?

+3

Я предполагаю, что вы знаете, что 0.1 является иррациональным у основания два, и будет _ всегда быть представлено неточно?Если вы хотите округлить до заданного количества десятичных знаков, вам потребуется десятичное представление, такое как строка. – 9000

+3

Обратите внимание, что это не факт Haskell, тождественное поведение будет найдено на всех языках, если вы не имеете дело с [произвольными типами точности] (https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic), которые редко * по умолчанию. Добро пожаловать в страшную часть о работе с финансовыми расчетами в программном обеспечении. –

ответ

7

Это не проблема haskell, а проблема с плавающей запятой. Поскольку каждое число с плавающей запятой реализовано в конечном числе бит, существуют числа, которые не могут быть представлены полностью точно. Вы также можете увидеть это, вычислив 0.1 + 0.2, что неудобно возвращает 0.30000000000000004 вместо 0.3. Это связано с тем, как числа с плавающей запятой реализованы для вашей языковой и аппаратной архитектуры.

Решение заключается в продолжении использования функции roundTo для вычисления (это так же точно, как вы получите без специальных библиотек), но если вы хотите распечатать ее на экране, тогда вы должны использовать форматирование строк, например, Text.Printf.printf функция. Вы можете указать количество цифр, округлить при преобразовании в строку с чем-то вроде

import Text.Printf 

roundToStr :: (PrintfArg a, Floating a) => Int -> a -> String 
roundToStr n f = printf ("%0." ++ show n ++ "f") f 

Но, как я уже говорил, это будет возвращать строку, а не число.

EDIT:

Лучшим способом может быть

roundToStr :: (PrintfArg a, Floating a) => Int -> a -> String 
roundToStr n f = printf (printf "%%0.%df" n) f 

, но я не протестированные, чтобы увидеть, какие на самом деле быстрее. Оба будут работать точно так же, хотя.

EDIT 2:

Как @augustss указал, что вы можете сделать это еще проще с помощью только

roundToStr :: (PrintfArg a, Floating a) => Int -> a -> String 
roundToStr = printf "%0.*f" 

, которая использует правило форматирования, что я раньше не догадывались.

+2

Именно поэтому форматирование в формате printf допускает «*» как количество цифр. – augustss

+0

@augustss Спасибо, что указали на это, я не знал об этом правиле форматирования. Я отредактирую его в сообщении. – bheklilr

10

Я понимаю, что этот вопрос был опубликован почти 2 года назад, но я думал, что у меня есть ответ, который не требует преобразования строк.

-- x : number you want rounded, n : number of decimal places you want... 
truncate' :: Double -> Int -> Double 
truncate' x n = (fromIntegral (floor (x * t)))/t 
    where t = 10^n 

-- How to answer your problem... 
λ truncate' 1.0014 3 
1.001 

-- 2 digits of a recurring decimal please... 
λ truncate' (1/3) 2 
0.33 

-- How about 6 digits of pi? 
λ truncate' pi 6 
3.141592 

Я не проверял его тщательно, так что если вы найти номера это не работает, дайте мне знать!

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