views:

289

answers:

3

At work today, we threw together this attempt:

xquery version "1.0";
declare option saxon:output             "omit-xml-declaration=yes";
declare variable $x := 99;

string-join(
    for $b in (128,64,32,16,8,4,2,1)
    let $xm := $x mod ($b*2)
    return
        if ( $xm >= $b ) then "1" else "0"
, "")

Do you have a better way?

Taking Oliver's answer, I have made the reverse function.

declare function local:bin-byte($x as xs:string) as xs:unsignedByte
{
  let $binary-nibbles := ("0000", "0001", "0010", "0011", 
                          "0100", "0101", "0110", "0111",
                          "1000", "1001", "1010", "1011",
                          "1100", "1101", "1110", "1111")
  return xs:unsignedByte(
    (index-of( $binary-nibbles, substring($x,1,4) )-1) * 16
    + (index-of( $binary-nibbles, substring($x,5,4) )-1)
    )
};
+2  A: 

As a minor note, if you are returning text, not XML then you are probably better off setting method=text rather than omit-xml-declaration=yes, although in this case it makes no difference.

An alternative solution is using a lookup table:

declare function local:binary($x as xs:unsignedByte) as xs:string
{
  let $binary-nibbles := ("0000", "0001", "0010", "0011", 
                          "0100", "0101", "0110", "0111",
                          "1000", "1001", "1010", "1011",
                          "1100", "1101", "1110", "1111")
  return concat($binary-nibbles[$x idiv 16 + 1],
                $binary-nibbles[$x mod 16 + 1])
};
Oliver Hallam
We did think about this, but didn't try it. Beautifully simple and fast too I suspect (which could be important to us).Our output for our application will be XML or text. In this case, it is probably XML.
philcolbourn
should xs:byte be xs:unsignedByte?
philcolbourn
you're right; I have made this change
Oliver Hallam
+1  A: 

Recursive functions are clear if slower:

declare function local:decimal-to-binary ($d as xs:integer) as xs:string {
 if ($d > 0)
 then concat(local:decimal-to-binary(floor($d div 2)),$d mod 2)
 else ""
};

eg

local:decimal-to-binary(42)

with inverse:

declare function local:binary-to-decimal($b as xs:string) as xs:integer {
 if ($b ne "")
 then local:binary-to-decimal(substring($b, 1, string-length($b)- 1)) * 2 
       + number(substring ($b, string-length($b),1))
 else 0

};

local:binary-to-decimal(local:decimal-to-binary(42))

Chris Wallace
I do like recursion. I had to wrap the floor() function in a xs:integer() to get saxonb(?) xquery to run it. I also had to change number into xs:integer. This would be good if you only wanted the minimum length binary representation of the number.
philcolbourn
I tested the code in the eXist sandbox http://demo.exist-db.org/exist/sandbox/sandbox.xql - saxon must be stricter - could just say xs:integer($d div 2). To get a fixed length string, use string-pad:declare function local:decimal-to-binary($d as xs:integer,$length as xs:integer){ let $binary := local:decimal-to-binary($d) return concat (string-pad("0", $length - string-length($binary)),$binary)};local:decimal-to-binary(42,8)
Chris Wallace
The function above is valid XQuery - the function argument conversion rules should mean that the arguments are cast to xs:integers (as this is what the argument expects). I am suprised that saxon didn't like it.
Oliver Hallam
@ Oliver: I am using Saxon 9.1.0.6J from Saxonica. The error is XPTY0004: Required item type of first argument of local:decimal-to-binary() is xs:integer; supplied value has item type xs:decimal at local:decimal-to-binary() (file:dec-bin.xq#46) at local:decimal-to-binary() (file:dec-bin.xq#58)
philcolbourn
+1  A: 

The most efficient way I can think of doing the reverse (at least in XQSharp) is:

declare function local:binary-string-to-integer($binary as xs:string)
                   as xs:integer
{
  local:binary-codepoints-to-integer(string-to-codepoints($binary), 1, 0)
};

declare function local:binary-codepoints-to-integer(
                   $codepoints as xs:integer*,
                   $current-index as xs:integer,
                   $result as xs:integer)
                   as xs:integer
{
  let $current-codepoint := $codepoints[$current-index]
  return
    if (empty($current-codepoint))
    then $result
    else local:binary-codepoints-to-integer(
           $codepoints,
           $current-index + 1,
           2 * $result + $current-codepoint - string-to-codepoints("0"))
};

A quick performance test shows both methods to perform about the same when the query is interpreted, but this method is about 50% faster when the query is compiled. The recursive function method also has the benefit of not being limited to unsigned bytes.

Either way the runtime is about 10 microseconds so is nothing to be concerned about.

Oliver Hallam