Difference between revisions of "Hex"

From Second Life Wiki
Jump to navigation Jump to search
(quote the scientific repeatably measurable code size, not the astonishing plus four size naively measured by people who only add code to a nearly empty script)
(implement the Talk:Hex plan to reword for concise/ small/ fast for clarity, also strike the undefined term "operator" "executed")
Line 15: Line 15:
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.
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 [[Talk:Hex|discussion]] tab.
Caution: This page was a work in progress as of 2007-10. The specification, the implementations, the demo, the demo results, and the alternatives may not yet be totally consistent. See the [[Talk:Hex|discussion]] tab.
</div>
</div>
</div>
</div>
Line 23: Line 23:
== Implementations ==
== Implementations ==
<div style="padding: 0.5em;">
<div style="padding: 0.5em;">
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.
Here you see code that implements the hex function specification, demo code that calls the hex function, and a copy of exactly the results you should see when you run the demo.  


=== Clear & Conventional ===
You see three kinds of code that implement the one specification. The concise & conventional code explains and implements the specification. The small code reduces the code size reported by llGetFreeMemory. The fast code reduces the run time reported by llGetTimestamp.


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.
=== Concise & Conventional ===
 
You should choose this code when you care more about concise & conventional than about small & fast.
 
You should feel that this concise & conventional code is easy to review and modify. For example, you should immediately understand the comment that tells you 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. Also you should immediately see what results to expect this code to return for any input.


<pre>
<pre>
Line 60: Line 63:
         return "0x0"; // bits2nybbles(value) == "" when (value == 0)
         return "0x0"; // bits2nybbles(value) == "" when (value == 0)
     }
     }
     else // if (value > 0)
     else // if (0 < value)
     {
     {
         return "0x" + bits2nybbles(value);
         return "0x" + bits2nybbles(value);
Line 67: Line 70:
</pre>
</pre>


'''Size:''' 347 bytes<br/>
347 bytes of code reported by llGetFreeMemory -- more than the small code and more than the fast code
 
FIXME milliseconds of run time to calculate "-0x80000000" reported by llGetTimestamp -- more than the small code and more than the fast code


=== Small ===
=== Small ===


Here is a size optimized implementation. It has a reduced memory footprint but is slower then a Speed implementations.
You should choose this code when you care more about small & fast than about concise & conventional.
 
We wrote this small code to show how to reduce the code size reported by [[llGetFreeMemory]]. We think this small code produces the same results as the concise & conventional code. After adequate education & review, you should agree. Code to implement bits2nybbles may be smaller than the hexu code shown here.


<pre>
<pre>
Line 95: Line 102:
</pre>
</pre>


'''Size:''' 250 bytes<br/>
250 bytes of code reported by llGetFreeMemory -- less than the concise & conventional code and less than the fast code
 
FIXME milliseconds of run time to calculate "-0x80000000" reported by llGetTimestamp -- less than the concise & conventional code, but more than the fast code


=== Fast ===
=== 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.
You should choose this code when you care more about fast & small than about concise & conventional.
 
We wrote this fast code to show how to reduce the run time reported by [[llGetTimestamp]]. We think this fast code produces the same results as the concise & conventional code. After adequate education & review, you should agree. This code leaves as little work inside the loop as possible. This code might not be as fast as code that unrolls the loop completely.


<pre>
<pre>
Line 128: Line 139:
</pre>
</pre>


'''Size:''' 337 bytes<br/>
337 bytes of code reported by llGetFreeMemory - less than concise & conventional code, but more than the small code
 
FIXME milliseconds of run time to calculate "-0x80000000" reported by llGetTimestamp - less than the concise & conventional code and less than the small code


=====Demo=====
=====Demo=====


This demo script shows some interesting test cases for the hex function and the permission masks of the script.
Run this demo code to confirm that you get exactly the expected demo results below. We chose test cases that astonish people new to hex.
 
We also chose test cases that astonish people new to LSL permission masks. You'll get the permission mask results we show if you create a New Script to run this demo in. If instead you tried modifying some old script to run this demo, then you might have to edit its permission masks to get the demo results that we show here.


<pre>
<pre>
Line 164: Line 179:
=====Demo Results:=====
=====Demo Results:=====


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


<pre>
<pre>
Line 186: Line 201:
</pre>
</pre>


=====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.
</div>
</div>
</div>
</div>
Line 196: Line 209:
<div style="padding: 0.5em">
<div style="padding: 0.5em">


The first few implementations we present do conform to our specification.
The specification requires exactly the same results as the hex function of the Python scripting language.


Our specification requires exactly the same results as the hex function of the Python scripting language.
The specification reproduces how hex integer literals often appear in LSL script, conforming to such arbitrary and traditional AT&T C conventions as:
 
This specification reproduces how hex integer literals often appear in LSL script, conforming to such arbitrary and traditional AT&T C conventions as:


# return lower case a b c d e f rather than upper case A B C D E F,
# return lower case a b c d e f rather than upper case A B C D E F,
Line 213: Line 224:
Disputes over the detailed specification of the Python hex function appear buried deep within http://www.python.org/dev/peps/pep-0237/
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.
As you read this page, you have to wade thru more than one implementation because our LSL wiki serves more than one community of LSL authors. New authors need to see a concise & conventional exemplar, experts find appropriate uses for the small & fast exemplars.
 
We don't know how to write LSL that implements a hex function that is as concise as our most concise, as conventional as our most conventional, as small as our smallest, and as fast as our fastest, all simultaneously. We think that goal is an impossibility.
 
</div>
</div>
</div>
</div>
Line 221: Line 235:
== Alternatives ==
== Alternatives ==
<div style="padding: 0.5em;">
<div style="padding: 0.5em;">
You can make the code smaller and faster still whenever you can accept the cost of abandoning the conventional specification.


=== Different & Small - Signed Only ===
=== Different & Small - Signed Only ===


Rumour tells us this version returns results that differ somehow, I don't yet see the difference myself.
The history of this article says that this version somehow violates the specification, I don't yet see the difference myself. Possibly the intended contrast was with an unsigned hex implementation such as bits2nybbles or hexu.
 
Here is an optimized implementation that only uses a single function, this provides an additional reduction to the memory footprint.


<pre>
<pre>
Line 249: Line 263:
</pre>
</pre>


'''LSO Size:''' 197 bytes<br/>
197 bytes of code reported by llGetFreeMemory
 
FIXME milliseconds of run time to calculate "-0x80000000" reported by llGetTimestamp


=== Different & Small - Signed or Unsigned ===
=== Different & Small - Signed or Unsigned ===
Line 255: Line 271:
This version returns a signed or an unsigned result, whichever you prefer.
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.
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 -- an assumption entirely outside the specification.


<pre>
<pre>
Line 276: Line 292:
</pre>
</pre>


'''LSO Size:''' 200 bytes<br/>
200 bytes of code reported by llGetFreeMemory
 
TBD milliseconds of run time to calculate "-0x80000000" reported by llGetTimestamp


</div>
</div>
Line 291: Line 309:
* [[llIntegerToBase64]]
* [[llIntegerToBase64]]
* [[llBase64ToInteger]]
* [[llBase64ToInteger]]
* [[llGetFreeMemory]]
* [[llGetTimestamp]]
* [[llMessageLinked]]
* [[llMessageLinked]]
* [[llList2Integer]]
* [[llList2Integer]]

Revision as of 21:00, 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, the demo results, and the alternatives may not yet be totally consistent. See the discussion tab.

Implementations

Here you see code that implements the hex function specification, demo code that calls the hex function, and a copy of exactly the results you should see when you run the demo.

You see three kinds of code that implement the one specification. The concise & conventional code explains and implements the specification. The small code reduces the code size reported by llGetFreeMemory. The fast code reduces the run time reported by llGetTimestamp.

Concise & Conventional

You should choose this code when you care more about concise & conventional than about small & fast.

You should feel that this concise & conventional code is easy to review and modify. For example, you should immediately understand the comment that tells you 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. Also you should immediately see what results to expect this code to return for any input.

// 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 (0 < value)
    {
        return "0x" + bits2nybbles(value);
    }
}

347 bytes of code reported by llGetFreeMemory -- more than the small code and more than the fast code

FIXME milliseconds of run time to calculate "-0x80000000" reported by llGetTimestamp -- more than the small code and more than the fast code

Small

You should choose this code when you care more about small & fast than about concise & conventional.

We wrote this small code to show how to reduce the code size reported by llGetFreeMemory. We think this small code produces the same results as the concise & conventional code. After adequate education & review, you should agree. Code to implement bits2nybbles may be smaller than the hexu code shown here.

// 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);
}

250 bytes of code reported by llGetFreeMemory -- less than the concise & conventional code and less than the fast code

FIXME milliseconds of run time to calculate "-0x80000000" reported by llGetTimestamp -- less than the concise & conventional code, but more than the fast code

Fast

You should choose this code when you care more about fast & small than about concise & conventional.

We wrote this fast code to show how to reduce the run time reported by llGetTimestamp. We think this fast code produces the same results as the concise & conventional code. After adequate education & review, you should agree. This code leaves as little work inside the loop as possible. This code might not be as fast as code that unrolls the loop completely.

// 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);
}

337 bytes of code reported by llGetFreeMemory - less than concise & conventional code, but more than the small code

FIXME milliseconds of run time to calculate "-0x80000000" reported by llGetTimestamp - less than the concise & conventional code and less than the small code

Demo

Run this demo code to confirm that you get exactly the expected demo results below. We chose test cases that astonish people new to hex.

We also chose test cases that astonish people new to LSL permission masks. You'll get the permission mask results we show if you create a New Script to run this demo in. If instead you tried modifying some old script to run this demo, then you might have to edit its permission masks to get the demo results that we show here.

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 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

Design Rationale

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

The 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/

As you read this page, you have to wade thru more than one implementation because our LSL wiki serves more than one community of LSL authors. New authors need to see a concise & conventional exemplar, experts find appropriate uses for the small & fast exemplars.

We don't know how to write LSL that implements a hex function that is as concise as our most concise, as conventional as our most conventional, as small as our smallest, and as fast as our fastest, all simultaneously. We think that goal is an impossibility.

Alternatives

You can make the code smaller and faster still whenever you can accept the cost of abandoning the conventional specification.

Different & Small - Signed Only

The history of this article says that this version somehow violates the specification, I don't yet see the difference myself. Possibly the intended contrast was with an unsigned hex implementation such as bits2nybbles or hexu.

// 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;
}

197 bytes of code reported by llGetFreeMemory

FIXME milliseconds of run time to calculate "-0x80000000" reported by llGetTimestamp

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 -- an assumption entirely outside the specification.

// 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;
}

200 bytes of code reported by llGetFreeMemory

TBD milliseconds of run time to calculate "-0x80000000" reported by llGetTimestamp