Formatting binary values in Scala

2020-02-03 05:53发布

问题:

Does Scala have a built in formatter for binary data?

For example to print out: 00000011 for the Int value 3.

Writing one won't be difficult - just curious if it exists.

回答1:

scala> 3.toBinaryString
res0: String = 11

Scala has an implicit conversion from Int to RichInt which has a method toBinaryString. This function does not print the leading zeroes though.



回答2:

I don't know of a direct API method to do it, but here is one way of doing it:

def toBinary(i: Int, digits: Int = 8) =
    String.format("%" + digits + "s", i.toBinaryString).replace(' ', '0')


回答3:

8 digits for number 3 with leading zeros:

printf ("%08d", 3.toBinaryString.toInt)
00000011

Since Hosam Aly suggests to create a String as well, here is a method to do so:

def asNdigitBinary (source: Int, digits: Int): String = {
  val l: java.lang.Long = source.toBinaryString.toLong
  String.format ("%0" + digits + "d", l) }

In the general case, using a Long is more appropriate, since binary values get long very fast:

scala> asNdigitBinary (1024*512-1, 32)
res23: String = 00000000000001111111111111111111

So keep that in mind - a selfmade, recursive approach which generates digit by digit and fills them up in the end would be easily made to handle arbitrary values of BigInt.

def toBinDigits (bi: BigInt): String = { 
  if (bi == 0) "0" else toBinDigits (bi /2) + (bi % 2)}

def fillBinary (bi: BigInt, len: Int) = { 
  val s = toBinDigits (bi)
  if (s.length >= len) s 
  else (List.fill (len-s.length) ("0")).mkString ("") + s
}

It would be nice, if

def asNdigitBinary (source: Int, digits: Int): String = {
  val l = BigInt (source.toBinaryString.toLong) 
  String.format ("%0" + digits + "d", l)}

would work, but "%0Nd" does not match for BigInt digits. Maybe a Bugreport/Feature request should be made? But to Scala or Java?



回答4:

I usually use to prepend zeroes of the wanted length -1 and then just chop the rightmost characters:

"0000000" + 3.toBinaryString takeRight 8

This works fine for negative values as well.



回答5:

Here is one more way (old Java):

val x = 5
val str = Integer.toString(x,2)

Just like Lauri answer, it doesn't print leading zeros.



回答6:

The Scala standard library's built-in to-binary-digits String formatters (toBinaryString) for the integer types (Byte, Short, Char, Int, and Long) are very limited. And an implementation for Boolean isn't provided.

Additionally, for Byte and Short, the actual emitted format is wrong for negative values (as both forward to the Int.toBinaryString implementation which then 1 fills out to 32 characters, not the correct widths of 8 and 16 characters respectively).

Also, I have read through every answer here. And I learned quite a bit about the various ways to approach solving this problem. Ultimately, though, there wasn't a drop in solution which "just worked" within my current project. So...

I have created a single method implementation fixing and then enhancing all of the above inconsistencies, errors, and adds missing functionality. Now, if I could only figure out how to get this included in the Standard Library for 2.13 and Scala 3...

The size parameter has three domains of values. See the code comments for more precise details.

  1. size = 0 -> (DEFAULT) zero fill to the bit size of the containing type
  2. size < 0 -> model the default behavior of toBinaryString function already on Byte, Short, Char, Int, and Long - also fix the hidden upcast to Int for both Byte and Short
  3. size > 0 -> caller designated zero fill - ignored if the size is smaller than the length required to capture the 1 digit immediately to the left of the leftmost 0 digit (to preserve the sign)

    def toBinaryString[A <: AnyVal](value: A, size: Int = 0): String = {
      val zerosX64: String = //maximum possible number of leading zeros
        "0" * 64

      val (valueAsBinaryString, typeSize) =
        value match {
          case valueAlmostTyped: Boolean =>
            (if (valueAlmostTyped) "1" else "0", 1)
          case valueAlmostTyped: Byte =>
            (valueAlmostTyped.toByte.toBinaryString.takeRight(8), 8) //take() fixes hidden upcast to Int in Byte.toBinaryString
          case valueAlmostTyped: Short =>
            (valueAlmostTyped.toShort.toBinaryString.takeRight(16), 16) //take() fixes hidden upcast to Int in Short.toBinaryString
          case valueAlmostTyped: Char =>
            (valueAlmostTyped.toChar.toBinaryString, 16)
          case valueAlmostTyped: Int =>
            (valueAlmostTyped.toInt.toBinaryString, 32)
          case valueAlmostTyped: Long =>
            (valueAlmostTyped.toLong.toBinaryString, 64)
          case _ =>
            throw new IllegalArgumentException(s"toBinaryString not implemented for this type [${value.getClass.getSimpleName}] - only implemented for Boolean, Byte, Short, Char, Int, and Long")
        }

      val newSize =
        if (size < 0) //model and fix the behavior of existing toBinaryString function on Byte, Short, Char, Int, and Long, and add for Binary
          valueAsBinaryString.length
        else
          if (size == 0) //zero fill to the bit size of the containing type
            typeSize
          else
            if (valueAsBinaryString.length > size) //possibly override the caller specified custom size value as it is smaller than the resulting valueAsBinaryString itself
              if (valueAsBinaryString.take(valueAsBinaryString.length - size + 1).exists(_ == '0')) //only override if there isn't a zero dropped (which includes protecting the sign by ensuring if all 1s preceded the 0, at least a single one is preserved
                valueAsBinaryString.length
              else //caller specified custom value
                size
            else //caller specified custom value
              size
      ( (
            if (newSize > valueAsBinaryString.length)
              zerosX64.take(newSize - valueAsBinaryString.length)
            else
              ""
        )
        + valueAsBinaryString.takeRight(newSize)
      )
    }


回答7:

This will print the leading zeroes:

  def int2bin(i: Int, numPos: Int): String = {
    def nextPow2(i: Int, acc: Int): Int = if (i < acc) acc else nextPow2(i, 2 * acc)
    (nextPow2(i, math.pow(2,numPos).toInt)+i).toBinaryString.substring(1)
  }


回答8:

You can do something like this:

scala> val x = 3
x: Int = 3

scala> Integer.toString(x, 2)
res4: java.lang.String = 11

As with other suggestions, this doesn't have leading zeros...