舍入到Haskell中的特定位数

Phi*_*ipp 9 math 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)
Run Code Online (Sandbox Code Playgroud)

我正在使用该digs函数来确定逗号之前的数字位数以优化输入值(即将所有内容移到逗号之外,因此1.234变为0.1234 * 10^1)

roundTo函数似乎适用于大多数输入,但是对于某些输入,我得到奇怪的结果,例如roundTo 1.0014 4生成1.0010000000000001而不是1.001.

此示例中的问题是由计算1001 * 1.0e-3(返回1.0010000000000001)引起的

这只是Haskell的数字表示中的一个问题,我必须忍受或者是否有更好的方法将浮点数舍入到特定的数字长度?

sch*_*anq 14

我意识到这个问题已经发布了差不多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
Run Code Online (Sandbox Code Playgroud)

我没有彻底测试过,所以如果你找到数字,这不能让我知道!


bhe*_*ilr 6

This isn't a haskell problem as much as a floating point problem. Since each floating point number is implemented in a finite number of bits, there exist numbers that can't be represented completely accurately. You can also see this by calculating 0.1 + 0.2, which awkwardly returns 0.30000000000000004 instead of 0.3. This has to do with how floating point numbers are implemented for your language and hardware architecture.

The solution is to continue using your roundTo function for doing computation (it's as accurate as you'll get without special libraries), but if you want to print it to the screen then you should use string formatting such as the Text.Printf.printf function. You can specify the number of digits to round to when converting to a string with something like

import Text.Printf

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

但正如我所提到的,这将返回一个字符串而不是一个数字.

编辑:

一个更好的方法可能是

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

但我没有基准测试看哪个实际上更快.两者都将完全相同.

编辑2:

正如@augustss所指出的那样,你可以更加轻松地做到这一点

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

它使用我以前不知道的格式规则.

  • 这就是printf格式化允许'*'作为位数的原因. (2认同)