【问题标题】:Rounding to specific number of digits in Haskell在 Haskell 中四舍五入到特定位数
【发布时间】:2013-09-10 15:43:51
【问题描述】:

我正在尝试创建一个函数来将浮点数四舍五入到定义的数字长度。到目前为止,我想出的是:

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 数字表示中的一个问题,还是有更好的方法将浮点数舍入到特定长度的数字?

【问题讨论】:

  • 我假设您知道 0.1 在底数为 2 时是不合理的,并且会 总是 不精确地表示吗?如果要四舍五入到指定的小数位数,则需要十进制表示,例如字符串。
  • 请注意,这不是 Haskell 的事实,当不处理 arbitrary precision types 时,所有语言中都会发现相同的行为,而这些语言很少具有默认设置。欢迎来到关于在软件中处理财务计算的可怕部分。

标签: math haskell


【解决方案1】:

我意识到这个问题是在将近 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

我没有彻底测试过,所以如果你发现这不起作用的数字,请告诉我!

【讨论】:

    【解决方案2】:

    这不是一个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
    

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

    编辑:

    更好的方法可能是

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

    但我还没有进行基准测试,看看哪个实际上更快。两者的工作原理完全相同。

    编辑 2:

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

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

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

    【讨论】:

    • 这就是为什么 printf 格式允许使用 '*' 作为位数。
    • @augustss 感谢您向我指出这一点,我不知道该格式规则。我将其编辑到帖子中。
    【解决方案3】:

    我也认为避免字符串转换是要走的路;但是,我会修改以前的post(来自schanq)以使用round 而不是floor

    round' :: Double -> Integer -> Double
    round' num sg = (fromIntegral . round $ num * f) / f
        where f = 10^sg
    
    > round' 4 3.99999
    4.0
    > round' 4 4.00001
    4.0
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-09-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多