Difference between revisions of "Hex"

From Second Life Wiki
Jump to navigation Jump to search
(→‎Implementations: strike the loop costs, pending a link to how we reproduce those results - see Talk:Hex)
(→‎Alternatives: carry thru Strife's Talk:Hex suggestion to make the Demo more accessible by subordinating the less popular exemplars)
Line 130: Line 130:
'''LSO Size:''' 254 bytes<br/>
'''LSO Size:''' 254 bytes<br/>


=== Different & Small - Signed Only ===
=====Demo=====
 
Rumour tells us this version returns results that differ somehow, I don't yet see the difference myself.
 
Here is an optimized implementation that only uses a single function, this provides an additional reduction to the memory footprint.
 
<pre>
// http://wiki.secondlife.com/wiki/hex
 
string hex(integer value)
{
    string lead = "0x";
    if(value & 0x80000000)
    {//saves one byte over "value < 0" and is faster.
        lead = "-0x";
        value = -value;
    }
    string nybbles = "";
    do
    {
        integer index = value & 0xF;
        nybbles = llGetSubString("0123456789abcdef", index, index) + nybbles;
    } while ((value = (0xfffFFFF & (value >> 4))));
    return lead + nybbles;
}
</pre>
 
'''LSO Size:''' 201 bytes<br/>
 
=== Different & Small - Signed or Unsigned ===
 
This version returns a signed or an unsigned result, whichever you prefer.
 
Here is a version where the calling interface has been changed to allow for the two functions to be merged. This merge allows for a considerable savings in bytecode. This savings evaporates if the function is explicitly called from more than 10 places in the script (as compared to the double function size implementation). This of course assumes you need both sets of functionality.
 
<pre>
// http://wiki.secondlife.com/wiki/hex
 
string hex(integer value, integer signed)
{
    string lead = "0x";
    if((value & 0x80000000) && signed)
    {//saves one byte over "value < 0" and is faster.
        lead = "-0x";
        value = -value;
    }
    string nybbles = "";
    do //variable recycling is ugly but it saves bytecode
        nybbles = llGetSubString("0123456789abcdef", signed = (value & 0xF), signed) + nybbles;
    while ((value = (0xfffFFFF & (value >> 4))));
    return lead + nybbles;
}
</pre>
 
'''LSO Size:''' 204 bytes<br/>
</div>
</div>
 
<div id="box">
 
== Demo ==
<div style="padding: 0.5em;">


This demo script shows some interesting test cases for the hex function and the permission masks of the script.
This demo script shows some interesting test cases for the hex function and the permission masks of the script.


=====Code:=====
<pre>
<pre>
default
default
Line 224: Line 162:
</pre>
</pre>


=====Sample Results:=====
=====Demo Results:=====
 
Running the demo script to call your choice of code should produce exactly these results:
 
<pre>
<pre>
Hello
Hello
Line 273: Line 214:


There are multiple implementations because LSL is inadequate to produce a version that is optimal in the majority of popular use cases.
There are multiple implementations because LSL is inadequate to produce a version that is optimal in the majority of popular use cases.
</div>
</div>
<div id="box">
== Alternatives ==
<div style="padding: 0.5em;">
=== Different & Small - Signed Only ===
Rumour tells us this version returns results that differ somehow, I don't yet see the difference myself.
Here is an optimized implementation that only uses a single function, this provides an additional reduction to the memory footprint.
<pre>
// http://wiki.secondlife.com/wiki/hex
string hex(integer value)
{
    string lead = "0x";
    if(value & 0x80000000)
    {//saves one byte over "value < 0" and is faster.
        lead = "-0x";
        value = -value;
    }
    string nybbles = "";
    do
    {
        integer index = value & 0xF;
        nybbles = llGetSubString("0123456789abcdef", index, index) + nybbles;
    } while ((value = (0xfffFFFF & (value >> 4))));
    return lead + nybbles;
}
</pre>
'''LSO Size:''' 201 bytes<br/>
=== Different & Small - Signed or Unsigned ===
This version returns a signed or an unsigned result, whichever you prefer.
Here is a version where the calling interface has been changed to allow for the two functions to be merged. This merge allows for a considerable savings in bytecode. This savings evaporates if the function is explicitly called from more than 10 places in the script (as compared to the double function size implementation). This of course assumes you need both sets of functionality.
<pre>
// http://wiki.secondlife.com/wiki/hex
string hex(integer value, integer signed)
{
    string lead = "0x";
    if((value & 0x80000000) && signed)
    {//saves one byte over "value < 0" and is faster.
        lead = "-0x";
        value = -value;
    }
    string nybbles = "";
    do //variable recycling is ugly but it saves bytecode
        nybbles = llGetSubString("0123456789abcdef", signed = (value & 0xF), signed) + nybbles;
    while ((value = (0xfffFFFF & (value >> 4))));
    return lead + nybbles;
}
</pre>
'''LSO Size:''' 204 bytes<br/>
</div>
</div>
</div>
</div>

Revision as of 18:44, 17 October 2007

Function: string hex(integer value);

Returns the hexadecimal nybbles of the signed integer value in order. Specifically returns the nybbles from most to least significant, starting with the first nonzero nybble, folding every nybble to lower case, and beginning with the nonnegative prefix "0x" or the negative prefix "-0x".

Parameters:

• integer value signed value to be expressed as signed hex

Note: Results with eight nybbles begin always with one of the positive signed nybbles 1 2 3 4 5 6 7, never with a zero or unsigned nybble 0 8 9 A B C D E F.

Caution: This page was a work in progress as of 2007-10. The specification, the implementations, the demo, and the sample results may not yet be totally consistent. See the discussion tab.

Implementations

Below are several different versions of the same two functions, each has been optimized with a different use case. These use cases are titled with the common programing goals they highlight: Clear & Conventional, Fast, Small, and Different. Unfortunately with LSL it is typically very difficult if not impossible to write code that embodies all of these design goals but it is possible to achieve several of them.

The design specification only specifies one function with a specific interface, implementations that do not meet that requirement are marked as Different.

Clear & Conventional

You should feel this code is easy to review, and modify. Upon review it is easy to see this code conforms to the specification and is easy to read. Additionally the comments instruct the user on how to substitute the easy-to-read upper case A B C D E F nybbles of IBM style for the easy-to-type lower case a b c d e f nybbles of AT&T style. The cost of this readability and easy of use is that the code runs substantially slower and has a larger memory footprint. Every other implementation runs faster then this one.

// http://wiki.secondlife.com/wiki/hex

string XDIGITS = "0123456789abcdef"; // could be "0123456789ABCDEF"

string bits2nybbles(integer bits)
{
    string nybbles = "";
    while (bits)
    {
        integer lsbs = bits & 0xF;
        string nybble = llGetSubString(XDIGITS, lsbs, lsbs);
        nybbles = nybble + nybbles;
        bits = bits >> 4; // discard the least significant bits at right
        bits = bits & 0xfffFFFF; // discard the sign bits at left
    }
    return nybbles;
}

string hex(integer value)
{
    if (value < 0)
    {
        return "-0x" + bits2nybbles(-value);
    }
    else if (value == 0)
    {
        return "0x0"; // bits2nybbles(value) == "" when (value == 0)
    }
    else // if (value > 0)
    {
        return "0x" + bits2nybbles(value);
    }
}

LSO Size: 351 bytes

Fast

In this implementation the readability and size have been scarified for speed. At execution, from call to return the fewest number of operators are executed, specifically the loop has been setup to use the fewest number of operators per iteration. In the double function category, this is the fastest implementation.

// http://wiki.secondlife.com/wiki/hex

string XDIGITS = "0123456789abcdef"; // could be "0123456789ABCDEF"

string hexu(integer bits)
{
    integer index = (bits & 0xF);
    string nybbles = llGetSubString(XDIGITS, index, index);
    if ((bits = (0xfffFFFF & (bits >> 4))))
    {
        do
        {
            nybbles = llGetSubString(XDIGITS, index = (bits & 0xF), index) + nybbles;
        } while ((bits = (bits >> 4)));
    }
    return "0x" + nybbles;
}

string hex(integer value)
{
    //saves one byte over "value < 0" and is faster.
    if (value & 0x80000000) return "-" + hexu(-value);
    return hexu(value);
}

LSO Size: 341 bytes

Small

Here is a size optimized implementation. It has a reduced memory footprint but is slower then a Speed implementations.

// http://wiki.secondlife.com/wiki/hex

string hexu(integer bits)
{
    string nybbles = "";
    do
    {
        integer index = bits & 0xF;
        nybbles = llGetSubString("0123456789abcdef", index, index) + nybbles;
    } while ((bits = (0xfffFFFF & (bits >> 4))));
    return "0x" + nybbles;
}

string hex(integer value)
{
    //saves one byte over "value < 0" and is faster.
    if (value & 0x80000000) return "-" + hexu(-value);
    return hexu(value);
}

LSO Size: 254 bytes

Demo

This demo script shows some interesting test cases for the hex function and the permission masks of the script.

default
{
    state_entry()
    {
        llOwnerSay("Hello");
        llOwnerSay(hex(0) + " == 0");
        llOwnerSay(hex(0x00FEDC00 & -0x00FEDC00) + " == (0x00FEDC00 & -0x00FEDC00)");
        llOwnerSay(hex(1 << 30) + " == (1 << 30)");
        llOwnerSay(hex(0x80000000) + " == 0x80000000");
        llOwnerSay(hex(0xFEDC9876) + " == 0xFEDC9876");
        llOwnerSay(hex(-1) + " == -1");
        llOwnerSay(hex(0x123456789) + " == 0x123456789");
        llOwnerSay("OK");
        
        llOwnerSay("Hello again");
        string item = llGetScriptName();
        llOwnerSay(hex(llGetInventoryPermMask(item, MASK_BASE)) + " as base");
        llOwnerSay(hex(llGetInventoryPermMask(item, MASK_OWNER)) + " by owner");
        llOwnerSay(hex(llGetInventoryPermMask(item, MASK_GROUP)) + " by group");
        llOwnerSay(hex(llGetInventoryPermMask(item, MASK_EVERYONE)) + " by anyone");
        llOwnerSay(hex(llGetInventoryPermMask(item, MASK_NEXT)) + " by next owner");
        llOwnerSay("aka " + (string) llGetInventoryPermMask(item, MASK_NEXT));
        llOwnerSay("OK");
    }
}
Demo Results:

Running the demo script to call your choice of code should produce exactly these results:

Hello
0x0 == 0
0x400 == (0x00FEDC00 & -0x00FEDC00)
0x40000000 == (1 << 30)
-0x80000000 == 0x80000000
-0x123678a == 0xFEDC9876
-0x1 == -1
-0x1 == 0x123456789
OK
Hello again
0x7fffffff as base
0x7fffffff by owner
0x0 by group
0x0 by anyone
0x82000 by next owner
aka 532480
OK
Note:

If you are using an implementation that doesn't conform to the specification the demo script may not compile or give the same results.

Design Rationale

The first few implementations we present do conform to our specification.

Our specification requires exactly the same results as the hex function of the Python scripting language.

This specification reproduces how hex integer literals often appear in LSL script, conforming to such arbitrary and traditional AT&T C conventions as:

  1. return lower case a b c d e f rather than upper case A B C D E F,
  2. return a signed 31-bit result if negative, rather than an unsigned 32-bit result,
  3. omit the leading quads of zeroed bits, except returns "0x0" rather than "0x" when the result is zero,
  4. return a meaningless "0" before the "x", as LSL and C compilers require,
  5. return the "x" on the left as in LSL and C, not the "h" on the right as in Assembly code, and
  6. return the nybbles listed from most to least significant as in English, not listed from least to most significant as in Arabic.

Brief doc for the Python hex function appears buried deep within http://docs.python.org/lib/built-in-funcs.html

Disputes over the detailed specification of the Python hex function appear buried deep within http://www.python.org/dev/peps/pep-0237/

There are multiple implementations because LSL is inadequate to produce a version that is optimal in the majority of popular use cases.

Alternatives

Different & Small - Signed Only

Rumour tells us this version returns results that differ somehow, I don't yet see the difference myself.

Here is an optimized implementation that only uses a single function, this provides an additional reduction to the memory footprint.

// http://wiki.secondlife.com/wiki/hex

string hex(integer value)
{
    string lead = "0x";
    if(value & 0x80000000)
    {//saves one byte over "value < 0" and is faster.
        lead = "-0x";
        value = -value;
    }
    string nybbles = "";
    do
    {
        integer index = value & 0xF;
        nybbles = llGetSubString("0123456789abcdef", index, index) + nybbles;
    } while ((value = (0xfffFFFF & (value >> 4))));
    return lead + nybbles;
}

LSO Size: 201 bytes

Different & Small - Signed or Unsigned

This version returns a signed or an unsigned result, whichever you prefer.

Here is a version where the calling interface has been changed to allow for the two functions to be merged. This merge allows for a considerable savings in bytecode. This savings evaporates if the function is explicitly called from more than 10 places in the script (as compared to the double function size implementation). This of course assumes you need both sets of functionality.

// http://wiki.secondlife.com/wiki/hex

string hex(integer value, integer signed)
{
    string lead = "0x";
    if((value & 0x80000000) && signed)
    {//saves one byte over "value < 0" and is faster.
        lead = "-0x";
        value = -value;
    }
    string nybbles = "";
    do //variable recycling is ugly but it saves bytecode
        nybbles = llGetSubString("0123456789abcdef", signed = (value & 0xF), signed) + nybbles;
    while ((value = (0xfffFFFF & (value >> 4))));
    return lead + nybbles;
}

LSO Size: 204 bytes