如何在Haskell中将整数文字打印成二进制或十六进制?

70
如何在Haskell中以二进制或十六进制打印整数常量?
printBinary 5 => "0101"

printHex 5 => "05"

哪些库/函数可以实现这个功能?

我发现了Numeric模块及其showIntAtBase函数,但是一直无法正确使用它。

> :t showIntAtBase 

showIntAtBase :: (Integral a) => a -> (Int -> Char) -> a -> String -> String
10个回答

101

数值模块包含多个用于以不同进制显示整数类型的函数,包括showIntAtBase。以下是一些使用示例:

import Numeric (showHex, showIntAtBase)
import Data.Char (intToDigit)

putStrLn $ showHex 12 "" -- prints "c"
putStrLn $ showIntAtBase 2 intToDigit 12 "" -- prints "1100"

11
对于像我这样懒惰的人,如果没有向下滚动查看的话,使用printf示例会更加简洁、灵活,并且可以执行其他有用的操作,例如提供一个固定长度的字符串和所有其他printf功能。与上述内容不同,只需输入以下内容:printf "%032b" 5 - mozboz
24
在 Haskell 中,printf 函数更像是一个魔术技巧,而不是严肃代码中应该使用的函数。格式字符串在运行时被解析(可能会导致运行时错误),整个机制有点慢。 - dfeuer
3
没有为showIntAtBase提供补零输出的变体,这真是遗憾。毕竟,如果你正在以二进制或十六进制格式进行格式化,那么你不太可能想要删除所有前导零。 - Andrew Thaddeus Martin

41
您也可以使用printf包中的printf来使用C语言风格的格式描述符格式化输出:
import Text.Printf

main = do

    let i = 65535 :: Int

    putStrLn $ printf "The value of %d in hex is: 0x%08x" i i
    putStrLn $ printf "The html color code would be: #%06X" i
    putStrLn $ printf "The value of %d in binary is: %b" i i

输出:

十六进制下65535的值为: 0x0000ffff
对应的HTML颜色代码为: #00FFFF
二进制下65535的值为: 1111111111111111


1
printf "The value of %d in hex is: 0x%08x" i i 是可以的,因为printf既可以是 IO() 也可以是 String - wuxb
1
是的,确实如此。我只是想让它明显一些,它可以作为一个返回字符串的纯函数使用。 - Martin Lütke
值得一提的是,Text.Printf 现在已经在 base 中了。 - Dominik Schrempf

29

如果你导入NumericData.Char模块,你可以这样做:

showIntAtBase 2 intToDigit 10 "" => "1010"
showIntAtBase 16 intToDigit 1023 "" => "3ff"
这将适用于任何基数,最多可达16进制,因为这就是 intToDigit 的工作范围。上面示例中额外的空字符串参数原因是 showIntAtBase 返回类型为 ShowS 的函数,它将显示表示连接到现有字符串上。

8

您可以使用以下代码将整数转换为二进制:

decToBin x = reverse $ decToBin' x
  where
    decToBin' 0 = []
    decToBin' y = let (a,b) = quotRem y 2 in [b] ++ decToBin' a

GHCi中的使用方法:

Prelude> decToBin 10
[1,0,1,0]

最后一行加上 b:(decToBIn a) 不是更好吗? - prince
0不应该返回0吗?decToBin' 0 = [0]也许是这样的? - Jaseem

4

十六进制可以用前缀0x表示,二进制可以用前缀0b表示,例如:

> 0xff
255
>:set -XBinaryLiterals
> 0b11
3

请注意,二进制需要启用BinaryLiterals扩展。

3

喜欢一行代码的人的愚蠢解决方案:

(\d -> let fix f = let {x = f x} in x in fmap (\n -> "0123456789abcdef" !! n) (fix (\f l n -> if n == 0 then l :: [Int] else let (q, r) = quotRem n 16 in f (r:l) q) [] d)) 247

一行代码的核心是:
quotRem 247 16

为了更加清晰,您也可以将以下内容放入文件中:
#!/usr/bin/env stack
{- stack script --resolver lts-12.1 -}
-- file: DecToHex.hs

module Main where

import System.Environment

fix :: (a -> a) -> a
fix f = let {x = f x} in x

ff :: ([Int] -> Int -> [Int]) -> [Int] -> Int -> [Int]
ff = \f l n ->
  if n == 0
  then l
  else
    let (q, r) = quotRem n 16
    in f (r:l) q

decToHex :: Int -> String
decToHex d =
  fmap (\n -> "0123456789abcdef" !! n)
  (fix ff [] d)

main :: IO ()
main =
  getArgs >>=
  putStrLn . show . decToHex . read . head

并使用以下命令执行脚本:

stack runghc -- DecToHex.hs 247

我只使用定点运算符作为一个定点运算符的示例;也因为它允许我严格从底向上构建单行代码。(注意:不鼓励底向上开发。)
参考文献: 堆栈脚本语法, 命令行参数, fix 运算符定义.

我认为你可以使用 ("0123456789abcdef" !!) 代替 (\n -> "0123456789abcdef" !! n) - Solomon Ucko

3
你可以定义自己的递归函数,如下所示:
import Data.Char (digitToInt)
import Data.Char (intToDigit)

-- generic function from base to decimal
toNum :: [Char] -> Int -> (Char -> Int) -> Int
toNum [] base map = 0
toNum s  base map = base * toNum (init(s)) base map + map(last(s))

-- generic function from decimal to base k
toKBaseNum :: Int -> Int -> (Int -> Char) -> [Char]
toKBaseNum x base map | x < base  = [map x]
                      | otherwise = toKBaseNum (x `div` base) base map ++ [map(x `mod` base)]


-- mapping function for hex to decimal
mapHexToDec :: Char -> Int
mapHexToDec x | x == 'A' = 10
              | x == 'B' = 11
              | x == 'C' = 12
              | x == 'D' = 13
              | x == 'E' = 14
              | x == 'F' = 15
              | otherwise = digitToInt(x) :: Int

-- map decimal to hex
mapDecToHex :: Int -> Char
mapDecToHex x | x < 10 = intToDigit(x)
              | x == 10 = 'A'
              | x == 11 = 'B'
              | x == 12 = 'C'
              | x == 13 = 'D'
              | x == 14 = 'E'
              | x == 15 = 'F'

-- hex to decimal
hexToDec :: String -> Int
hexToDec [] = 0
hexToDec s = toNum s 16 mapHexToDec

-- binary to decimal
binToDec :: String -> Int
binToDec [] = 0
binToDec s  = toNum s 2 (\x -> if x == '0' then 0 else 1)

-- decimal to binary
decToBin :: Int -> String
decToBin x = toKBaseNum x 2 (\x -> if x == 1 then '1' else '0')

-- decimal to hex
decToHex :: Int -> String
decToHex x = toKBaseNum x 16 mapDecToHex

说明: 如您所见,toNum函数使用给定的基数和映射函数将k进制值转换为十进制。映射函数将特殊字符映射到十进制值(例如十六进制中的A=10,B=11等)。对于二进制映射,您也可以像在binToDec中看到的那样使用lambda表达式。
toKBaseVal函数则相反,将十进制值转换为k进制值。同样,我们需要一个映射函数,该函数将从十进制值转换为k进制值的相应特殊字符。
作为测试,您可以输入:
binToDec(decToBin 7) = 7

假设您想要将十进制转换为八进制:
-- decimal to octal
decToOct :: Int -> String
decToOct x = toKBaseNum x 8 (\x -> intToDigit(x))

再次强调,我使用的只是lambda表达式,因为映射很简单:只需将整数映射到数字。

希望这能有所帮助!祝编程愉快!


1

这是一个简单、高效、不受基础限制的未授权实现:

convertToBase :: Word8 -> Integer -> String
convertToBase b n
    | n < 0              = '-' : convertToBase b (-n)
    | n < fromIntegral b = [(['0'..'9'] ++ ['A' .. 'Z']) !! fromIntegral n]
    | otherwise          = let (d, m) = n `divMod` fromIntegral b in convertToBase b d ++ convertToBase b m

你需要导入 Data.Word 才能使用 Word8(它可以尽可能地限制值的范围),并且你经常需要使用 fromIntegral(如果只有自动类型转换就好了…)。

所有其他答案都忽略了“Data.Word”。让我们真实一点 - 一旦你进行“位操作”,例如使用“Data.Bits”,你更有可能想要查看十六进制或二进制数字,并且你更有可能不使用“Int”而是使用“Word8,Word16,..”。+1 - BitTickler
虽然他选择使用Word8作为基础而不是数字有些奇怪...但这可以改进...如果它适用于所有“Bits a”,那就更好了。 - BitTickler

0

在使用text时,我建议使用text-show包,其中包括:

例如,将一个 Integer 转换为二进制的 Text
{-# LANGUAGE OverloadedStrings #-}

import TextShow (toText)
import TextShow.Data.Integral (showbBin)

toBinary :: Integer -> Text
toBinary n = toText . showbBin

> toBinary 6 == "110"

也许您想要添加一个“Text”前缀。 “Builder”可以帮助您高效地构建“Text”; 它是个单子。
toBinaryWithPrefix :: Text -> Integer -> Text
toBinaryWithPrefix prefix n = toText $ fromText prefix <> showbBin n

欲了解更多信息,请查看 Hackage 上提供的 TextShowTextShow.Data.Integral 模块。


0
使用 FiniteBits 类:
import Data.Bits (FiniteBits, finiteBitSize, testBit, shiftR)

showBits :: FiniteBits a => a -> String
showBits bits =
  go (finiteBitSize bits - 1) where
    go shift =
      if shift >= 0
        then
          let bit = if testBit (shiftR bits shift) 0 then '1' else '0'
          in bit : go (pred shift)
        else
          ""

例子:

showBits (4 :: Word8) => "00000100"

showBits (50 :: Int16) => "0000000000110010"

showBits (-127 :: Int32) => "11111111111111111111111110000001"


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接