Difference between revisions of "Float2Sci"

From Second Life Wiki
Jump to navigation Jump to search
m
(→‎Code: updated to colorized code display)
 
(13 intermediate revisions by 2 users not shown)
Line 1: Line 1:
A script for passing floats through strings without loosing precision.
A script for passing floats through strings without losing precision.
Wrote this ages ago, it isn't very fast but it is accurate. If you want fast and accurate but don't care so much about human readable try [[Float2Hex]].
Wrote this ages ago, it isn't very fast but it is accurate. If you want fast and accurate but don't care so much about human readable try [[Float2Hex]].


Works flawlessly and LSL can parse directly to floats again without any special code :-) just use a (float) typecast.
Works flawlessly and LSL can parse directly to floats again without any special code :-) just use a (float) typecast. In Mono the (float) typecast has a bug where anything that would be in the denormalized range gets mapped to zero. Nothing I can do to fix this or work around it.
 
This code, and it's comments, predate Mono by several years, what the comments have to say is true for LSO, not so much for Mono.


'''Changes:'''
'''Changes:'''
* Cannot assume that optical will work for numbers with large magnitudes. This was necessitated by {{Jira|SCR-397}} which was an unnoticed breaking change made during the switch over to Mono.
* Fixed: Encoding not working in Mono for denormalized range. I have to use llPow instead of (float) typecast.
* Prior versions can be found here: http://forums.secondlife.com/showthread.php?t=28006
* Fixed: Cannot assume that optical will work for numbers with large magnitudes. This was necessitated by {{Jira|SCR-397}} which was an unnoticed breaking change made during the switch over to Mono.
* Prior versions can be found here: http://forums-archive.secondlife.com/15/28/28006/1.html


==Code==
==Code==
<lsl>
<source lang="lsl2">
string Float2Sci(float input)
string Float2Sci(float input)
{// LSLEditor Unsafe, LSO Safe, Mono Safe
{// LSLEditor Unsafe, LSO Safe, Mono Safe
Line 17: Line 20:
     float frac = llFabs(input);//we put the negative back at the end.
     float frac = llFabs(input);//we put the negative back at the end.
     string mantissa = (string)frac;//this may be a string of about 47 characters long.
     string mantissa = (string)frac;//this may be a string of about 47 characters long.
    if(!~llSubStringIndex(mantissa, ".")) return (string)input; //NaN and Infinities, it's quick, it's easy.
     integer exponent = -6;//default exponent for optical method
     integer exponent = -6;//default exponent for optical method
     if(frac != (float)(mantissa = llDeleteSubString(mantissa, -7, -7)))
     if(frac == (float)mantissa){
     {//optical failed
        mantissa = llDeleteSubString(mantissa, -7, -7);//remove the decimal point
        //Ugly Math version; ugly in the sense that it is slow and not as elegant as working with it as a string.
        jump optical;
        //A) calculate the exponent via approximation of C log2()
    }
        //B) use kludge to avert fatal error in approximation of log2 result (only a problem with values >= 0x1.FFFFF8p127)
     //optical failed
        //      the exponent is sometimes reported as 128, which will bork float math, so we subtract the test for 128.
    //Ugly Math version; ugly in the sense that it is slow and not as elegant as working with it as a string.
        //      max_float btw is 0x1.FFFFFEp127, so we are only talking a very small number of numbers.
    //A) calculate the exponent via approximation of C log2()
        //C) normalize the float with questionable exponent
    //B) use kludge to avert fatal error in approximation of log2 result (only a problem with values >= 0x1.FFFFF8p127)
        //D) calculate rounding error left from log2 approximation and add to normalization value.
    //      the exponent is sometimes reported as 128, which will bork float math, so we subtract the test for 128.
        //      the '|' acts like a '+' in this instance but saves us one byte.
    //      max_float btw is 0x1.FFFFFEp127, so we are only talking a very small number of numbers.
        integer position = (24 | (3 <= frac)) - (integer)( //D
    //C) normalize the float with questionable exponent
                frac /= (float)("0x1p"+(string)( //C
    //D) calculate rounding error left from log2 approximation and add to normalization value.
                exponent = (exponent - (( //B
    //      the '|' acts like a '+' in this instance but saves us one byte.
                    exponent = llFloor(llLog(frac) / 0.69314718055994530941723212145818) //A
    integer position = (24 | (3 <= frac)) - (integer)( //D
                ) == 128))
            frac /= llPow(2.0, //C
                ))
            exponent = (exponent - (( //B
                );
                exponent = llFloor(llLog(frac) / 0.69314718055994530941723212145818) //A
            ) == 128))
            )
            );
 
    //this pushes the float into the integer buffer exactly.
    //since the shift is within integer range, we don't need to make a float.
    integer int = (integer)(frac * (1 << position));
    integer target = (integer)(frac = 0.0);//since the float is in the integer buffer, we need to clear the float buffer.
      
      
        //this pushes the float into the integer buffer exactly.
    //we don't use a traditional while loop, and instead opt for a do-while, because it's faster
        //since the shift is within integer range, we don't need to make a float.
    //since we may have to do about 128 iteration, this savings is important,
        integer int = (integer)(frac * (1 << position));
    //the exponent needs one final adjustment because of the shift, we do it here to save memory & it's faster.
        integer target = (integer)(frac = 0.0);//since the float is in the integer buffer, we need to clear the float buffer.
   
       
    //The two loops try to make exponent == position by shifting and multiplying.
        //we don't use a traditional while loop, and instead opt for a do-while, because it's faster
    //when they are equal, then this should be true ((int * llPow(10, exponent)) == llFabs(input))
        //since we may have to do about 128 iteration, this savings is important,
    //That is of course assuming that the llPow(10, exponent) result has enough precision.
        //the exponent needs one final adjustment because of the shift, we do it here to save memory & it's faster.
   
       
    //We recycle position for these loops as a temporary buffer. This is so we can save a few operations.
        //The two loops try to make exponent == position by shifting and multiplying.
    //If we didn't, then we could actually optimize the variable out of the code; though it would be slower.
        //when they are equal, then this should be true ((int * llPow(10, exponent)) == llFabs(input))
    if(target > (exponent -= position))
        //That is of course assuming that the llPow(10, exponent) result has enough precision.
    {//apply the rest of the bit shift if |input| < 1
       
        do
        //We recycle position for these loops as a temporary buffer. This is so we can save a few operations.
        {
        //If we didn't, then we could actualy optimize the variable out of the code; though it would be slower.
            if(int < 0x19999999)//(0x80000000 / 5)
        if(target > (exponent -= position))
            {//won't overflow, multiply in 5
        {//apply the rest of the bit shift if |input| < 1
                int = int * 5 + (position = (integer)(frac *= 5.0));
            do
                frac -= (float)position;
            {
                target = ~-target;
                if(int < 0x19999999)//(0x80000000 / 5)
            }
                {//won't overflow, multiply in 5
            else
                    int = int * 5 + (position = (integer)(frac *= 5.0));
            {//overflow predicted, divide by 2
                    frac -= (float)position;
                frac = (frac + (int & 1))/2;
                    target = ~-target;
                int = int >> 1;
                }
                exponent = -~exponent;
                else
             }
                {//overflow predicted, devide by 2
         }while(target ^ exponent);
                    frac = (frac + (int & 1))/2;
                    int = int >> 1;
                    exponent = -~exponent;
                }
             }while(target ^ exponent);
        }
         else if(target ^ exponent)//target < exponent
        {//apply the rest of the bit shift if |input| > 1
            do
            {
                if(int < 0x40000000) //(0x80000000 / 2)
                {//won't overflow, multiply in 2
                    int = (int << 1) + (position = (integer)(frac *= 2.0));
                    frac -= (float)position;
                    exponent = ~-exponent;
                }
                else
                {//overflow predicted, divide by 5
                    frac = (frac + int%5) / 5.0;
                    int /= 5;
                    target = -~target;
                }
            }while(target ^ exponent);
        }
        //int is now properly calculated, it holds enough data to accurately describe the input in conjunction with exponent.
        //we feed this through optical to clean up the answer.
        mantissa = (string)int;
     }
     }
    else if(target ^ exponent)//target < exponent
    {//apply the rest of the bit shift if |input| > 1
        do
        {
            if(int < 0x40000000) //(0x80000000 / 2)
            {//won't overflow, multiply in 2
                int = (int << 1) + (position = (integer)(frac *= 2.0));
                frac -= (float)position;
                exponent = ~-exponent;
            }
            else
            {//overflow predicted, divide by 5
                frac = (frac + int%5) / 5.0;
                int /= 5;
                target = -~target;
            }
        }while(target ^ exponent);
    }
    //int is now properly calculated, it holds enough data to accurately describe the input in conjunction with exponent.
    //we feed this through optical to clean up the answer.
    mantissa = (string)int;
   
    @optical;
     //it's not an issue that we may be jumping over the initialization of some of the variables,
     //it's not an issue that we may be jumping over the initialization of some of the variables,
     //we initialize everything we use here.
     //we initialize everything we use here.
      
      
     //to accurately describe a float you only need 9 decimal places; so we throw the extra's away
     //to accurately describe a float you only need 9 decimal places; so we throw the extras away
     if(9 < (target = position = llStringLength(mantissa)))
     if(9 < (target = position = llStringLength(mantissa)))
         position = 9;
         position = 9;
Line 117: Line 125:
     return mantissa;
     return mantissa;
}
}
</lsl>
</source>


===LSLEditor===
===LSLEditor===
Some people will want to run this in LSLEditor where the execution order is reversed and where booleans aren't integers.
Some people will want to run this in LSLEditor where the execution order is reversed and where booleans aren't integers.
Do keep in mind this will only work support float precision, not double precision. I have no intention of adding double precision.
Do keep in mind this will only work support float precision, not double precision. I have no intention of adding double precision.
 
<source lang="lsl2">string Float2Sci(float input)
<lsl>
string Float2Sci(float input)
{// LSLEditor Safe, LSO Safe, Mono Safe
{// LSLEditor Safe, LSO Safe, Mono Safe
     if(input == 0.0)//handles negative zero
     if(input == 0.0)//handles negative zero
Line 132: Line 138:
     string mantissa = (string)frac;//this may be a string of about 47 characters long.
     string mantissa = (string)frac;//this may be a string of about 47 characters long.
     integer exponent = -6;//default exponent for optical method
     integer exponent = -6;//default exponent for optical method
     if(frac != (float)(mantissa = llDeleteSubString(mantissa, -7, -7)))
     if(frac == (float)mantissa){
    {//optical failed
        mantissa = llDeleteSubString(mantissa, -7, -7);//remove the decimal point
        //Ugly Math version; ugly in the sense that it is slow and not as elegant as working with it as a string.
         jump optical;
        //A) calculate the exponent via approximation of C log2()
        //B) use kludge to avert fatal error in approximation of log2 result (only a problem with values >= 0x1.FFFFF8p127)
        //      the exponent is sometimes reported as 128, which will bork float math, so we subtract the test for 128.
        //      max_float btw is 0x1.FFFFFEp127, so we are only talking a very small number of numbers.
        //      but max_double is much larger but we aren't supporting those, we don't have the precision.
        //C) normalize the float with questionable exponent
        //D) calculate rounding error left from log2 approximation and add to normalization value.
        //      the '|' acts like a '+' in this instance but saves us one byte.
       
        if((exponent = llFloor(llLog(frac) / 0.69314718055994530941723212145818)) >= 128)//A
            exponent = ~-exponent;//B
        frac /= (float)("0x1p"+(string)(exponent));//C
        integer position = (24 | (integer)(3 <= frac)) - (integer)(frac); //D
       
        //this pushes the float into the integer buffer exactly.
        //since the shift is within integer range, we don't need to make a float.
        integer int = (integer)(frac * (1 << position));
        integer target = (integer)(frac = 0.0);//since the float is in the integer buffer, we need to clear the float buffer.
       
        //we don't use a traditional while loop, and instead opt for a do-while, because it's faster
        //since we may have to do about 128 iteration, this savings is important,
        //the exponent needs one final adjustment because of the shift, we do it here to save memory & it's faster.
       
        //The two loops try to make exponent == position by shifting and multiplying.
         //when they are equal, then this should be true ((int * llPow(10, exponent)) == llFabs(input))
        //That is of course assuming that the llPow(10, exponent) result has enough precision.
       
        //We recycle position for these loops as a temporary buffer. This is so we can save a few operations.
        //If we didn't, then we could actually optimize the variable out of the code; though it would be slower.
        if(target > (exponent -= position))
        {//apply the rest of the bit shift if |input| < 1
            do
            {
                if(int < 0x19999999)//(0x80000000 / 5)
                {//won't overflow, multiply in 5
                    int = int * 5 + (position = (integer)(frac *= 5.0));
                    frac -= (float)position;
                    target = ~-target;
                }
                else
                {//overflow predicted, devide by 2
                    frac = (frac + (int & 1))/2;
                    int = int >> 1;
                    exponent = -~exponent;
                }
            }while(target ^ exponent);
        }
        else if(target ^ exponent)//target < exponent
        {//apply the rest of the bit shift if |input| > 1
            do
            {
                if(int < 0x40000000) //(0x80000000 / 2)
                {//won't overflow, multiply in 2
                    int = (int << 1) + (position = (integer)(frac *= 2.0));
                    frac -= (float)position;
                    exponent = ~-exponent;
                }
                else
                {//overflow predicted, divide by 5
                    frac = (frac + int%5) / 5.0;
                    int /= 5;
                    target = -~target;
                }
            }while(target ^ exponent);
        }
        //int is now properly calculated, it holds enough data to accurately describe the input in conjunction with exponent.
        //we feed this through optical to clean up the answer.
        mantissa = (string)int;
     }
     }
   
    //optical failed
    //Ugly Math version; ugly in the sense that it is slow and not as elegant as working with it as a string.
    //A) calculate the exponent via approximation of C log2()
    //B) use kludge to avert fatal error in approximation of log2 result (only a problem with values >= 0x1.FFFFF8p127)
    //      the exponent is sometimes reported as 128, which will bork float math, so we subtract the test for 128.
    //      max_float btw is 0x1.FFFFFEp127, so we are only talking a very small number of numbers.
    //      but max_double is much larger but we aren't supporting those, we don't have the precision.
    //C) normalize the float with questionable exponent
    //D) calculate rounding error left from log2 approximation and add to normalization value.
    //      the '|' acts like a '+' in this instance but saves us one byte.
   
    if((exponent = llFloor(llLog(frac) / 0.69314718055994530941723212145818)) >= 128)//A
        exponent = ~-exponent;//B
    frac /= llPow(2.0, exponent);//C
    integer position = (24 | (integer)(3 <= frac)) - (integer)(frac); //D
   
    //this pushes the float into the integer buffer exactly.
    //since the shift is within integer range, we don't need to make a float.
    integer int = (integer)(frac * (1 << position));
    integer target = (integer)(frac = 0.0);//since the float is in the integer buffer, we need to clear the float buffer.
   
    //we don't use a traditional while loop, and instead opt for a do-while, because it's faster
    //since we may have to do about 128 iteration, this savings is important,
    //the exponent needs one final adjustment because of the shift, we do it here to save memory & it's faster.
   
    //The two loops try to make exponent == position by shifting and multiplying.
    //when they are equal, then this should be true ((int * llPow(10, exponent)) == llFabs(input))
    //That is of course assuming that the llPow(10, exponent) result has enough precision.
   
    //We recycle position for these loops as a temporary buffer. This is so we can save a few operations.
    //If we didn't, then we could actually optimize the variable out of the code; though it would be slower.
    if(target > (exponent -= position))
    {//apply the rest of the bit shift if |input| < 1
        do
        {
            if(int < 0x19999999)//(0x80000000 / 5)
            {//won't overflow, multiply in 5
                int = int * 5 + (position = (integer)(frac *= 5.0));
                frac -= (float)position;
                target = ~-target;
            }
            else
            {//overflow predicted, divide by 2
                frac = (frac + (int & 1))/2;
                int = int >> 1;
                exponent = -~exponent;
            }
        }while(target ^ exponent);
    }
    else if(target ^ exponent)//target < exponent
    {//apply the rest of the bit shift if |input| > 1
        do
        {
            if(int < 0x40000000) //(0x80000000 / 2)
            {//won't overflow, multiply in 2
                int = (int << 1) + (position = (integer)(frac *= 2.0));
                frac -= (float)position;
                exponent = ~-exponent;
            }
            else
            {//overflow predicted, divide by 5
                frac = (frac + int%5) / 5.0;
                int /= 5;
                target = -~target;
            }
        }while(target ^ exponent);
    }
    //int is now properly calculated, it holds enough data to accurately describe the input in conjunction with exponent.
    //we feed this through optical to clean up the answer.
    mantissa = (string)int;
   
    @optical;
     //it's not an issue that we may be jumping over the initialization of some of the variables,
     //it's not an issue that we may be jumping over the initialization of some of the variables,
     //we initialize everything we use here.
     //we initialize everything we use here.
      
      
     //to accurately describe a float you only need 9 decimal places; so we throw the extra's away
     //to accurately describe a float you only need 9 decimal places; so we throw the extras away
     if(9 < (target = position = llStringLength(mantissa)))
      
        position = 9;
//    if(9 < (target = position = llStringLength(mantissa)))
//        position = 9;
    target = position = llStringLength(mantissa);
   
     //chop off the tailing zero's; we don't need them.
     //chop off the tailing zero's; we don't need them.
     do
     do
         position = ~-position;
         position = ~-position;
     while(llGetSubString(mantissa, position, position) == "0" && position);//faster then a while loop
     while((llGetSubString(mantissa, position, position) == "0") && !!position);//faster then a while loop
      
      
     //we do a bad thing, we recycle 'target' here, position is one less then target,
     //we do a bad thing, we recycle 'target' here, position is one less then target,
Line 231: Line 245:
         return "-" + mantissa;
         return "-" + mantissa;
     return mantissa;
     return mantissa;
}</source>
=== Testbed ===
<source lang="lsl2">
test(float a){
    string b = Float2Sci(a);
    llSay(0, llList2CSV([a, (float)b, b, a - (float)b, a == (float)b]));
}
default
{
    state_entry()
    {
        llListen(1, "", llGetOwner(), "");
        test(84932901.0);
        test(1.0);
        test(10.0);
    }
    listen(integer channel, string name, key id, string message)
    {
        test((float)message);
    }
}
}
</lsl>
</source>

Latest revision as of 08:16, 16 June 2016

A script for passing floats through strings without losing precision. Wrote this ages ago, it isn't very fast but it is accurate. If you want fast and accurate but don't care so much about human readable try Float2Hex.

Works flawlessly and LSL can parse directly to floats again without any special code :-) just use a (float) typecast. In Mono the (float) typecast has a bug where anything that would be in the denormalized range gets mapped to zero. Nothing I can do to fix this or work around it.

This code, and it's comments, predate Mono by several years, what the comments have to say is true for LSO, not so much for Mono.

Changes:

  • Fixed: Encoding not working in Mono for denormalized range. I have to use llPow instead of (float) typecast.
  • Fixed: Cannot assume that optical will work for numbers with large magnitudes. This was necessitated by SCR-397 which was an unnoticed breaking change made during the switch over to Mono.
  • Prior versions can be found here: http://forums-archive.secondlife.com/15/28/28006/1.html

Code

string Float2Sci(float input)
{// LSLEditor Unsafe, LSO Safe, Mono Safe
    if(input == 0.0)//handles negative zero
        return llDeleteSubString((string)input, -5, -1);//trim off the trailing zero's, don't need them.
    
    float frac = llFabs(input);//we put the negative back at the end.
    string mantissa = (string)frac;//this may be a string of about 47 characters long.
    if(!~llSubStringIndex(mantissa, ".")) return (string)input; //NaN and Infinities, it's quick, it's easy.
    integer exponent = -6;//default exponent for optical method
    if(frac == (float)mantissa){
        mantissa = llDeleteSubString(mantissa, -7, -7);//remove the decimal point
        jump optical;
    }
    //optical failed
    //Ugly Math version; ugly in the sense that it is slow and not as elegant as working with it as a string.
    //A) calculate the exponent via approximation of C log2()
    //B) use kludge to avert fatal error in approximation of log2 result (only a problem with values >= 0x1.FFFFF8p127)
    //       the exponent is sometimes reported as 128, which will bork float math, so we subtract the test for 128.
    //       max_float btw is 0x1.FFFFFEp127, so we are only talking a very small number of numbers.
    //C) normalize the float with questionable exponent
    //D) calculate rounding error left from log2 approximation and add to normalization value.
    //       the '|' acts like a '+' in this instance but saves us one byte.
    integer position = (24 | (3 <= frac)) - (integer)( //D
            frac /= llPow(2.0, //C
            exponent = (exponent - (( //B
                exponent = llFloor(llLog(frac) / 0.69314718055994530941723212145818) //A
            ) == 128))
            )
            );

    //this pushes the float into the integer buffer exactly.
    //since the shift is within integer range, we don't need to make a float.
    integer int = (integer)(frac * (1 << position));
    integer target = (integer)(frac = 0.0);//since the float is in the integer buffer, we need to clear the float buffer.
    
    //we don't use a traditional while loop, and instead opt for a do-while, because it's faster
    //since we may have to do about 128 iteration, this savings is important,
    //the exponent needs one final adjustment because of the shift, we do it here to save memory & it's faster.
    
    //The two loops try to make exponent == position by shifting and multiplying.
    //when they are equal, then this should be true ((int * llPow(10, exponent)) == llFabs(input))
    //That is of course assuming that the llPow(10, exponent) result has enough precision.
    
    //We recycle position for these loops as a temporary buffer. This is so we can save a few operations.
    //If we didn't, then we could actually optimize the variable out of the code; though it would be slower.
    if(target > (exponent -= position))
    {//apply the rest of the bit shift if |input| < 1
        do
        {
            if(int < 0x19999999)//(0x80000000 / 5)
            {//won't overflow, multiply in 5
                int = int * 5 + (position = (integer)(frac *= 5.0));
                frac -= (float)position;
                target = ~-target;
            }
            else
            {//overflow predicted, divide by 2
                frac = (frac + (int & 1))/2;
                int = int >> 1;
                exponent = -~exponent;
            }
        }while(target ^ exponent);
    }
    else if(target ^ exponent)//target < exponent
    {//apply the rest of the bit shift if |input| > 1
        do
        {
            if(int < 0x40000000) //(0x80000000 / 2)
            {//won't overflow, multiply in 2
                int = (int << 1) + (position = (integer)(frac *= 2.0));
                frac -= (float)position;
                exponent = ~-exponent;
            }
            else
            {//overflow predicted, divide by 5
                frac = (frac + int%5) / 5.0;
                int /= 5;
                target = -~target;
            }
        }while(target ^ exponent);
    }
    //int is now properly calculated, it holds enough data to accurately describe the input in conjunction with exponent.
    //we feed this through optical to clean up the answer.
    mantissa = (string)int;
    
    @optical;
    //it's not an issue that we may be jumping over the initialization of some of the variables,
    //we initialize everything we use here.
    
    //to accurately describe a float you only need 9 decimal places; so we throw the extras away
    if(9 < (target = position = llStringLength(mantissa)))
        position = 9;
    //chop off the tailing zero's; we don't need them.
    do; while(llGetSubString(mantissa, position, position) == "0" && (position = ~-position));//faster then a while loop
    
    //we do a bad thing, we recycle 'target' here, position is one less then target,
    //"target + ~position" is the same as "target - (position + 1)" saves 6 bytes.
    //this block of code actually does the cutting.
    if(target + ~position) mantissa = llGetSubString(mantissa, 0, position);
    
    //insert the decimal point (not strictly needed). We add the extra zero for aesthetics.
    //by adding in the decimal point, it simplifies some of the code.
    mantissa = llInsertString(mantissa, 1, llGetSubString(".0", 0, !position));

    //adjust exponent from having added the decimal place
    if((exponent += ~-target))
        mantissa += "e" + (string)exponent;
    //return with the correct sign.
    if(input < 0)
        return "-" + mantissa;
    return mantissa;
}

LSLEditor

Some people will want to run this in LSLEditor where the execution order is reversed and where booleans aren't integers. Do keep in mind this will only work support float precision, not double precision. I have no intention of adding double precision.

string Float2Sci(float input)
{// LSLEditor Safe, LSO Safe, Mono Safe
    if(input == 0.0)//handles negative zero
        return llDeleteSubString((string)input, -5, -1);//trim off the trailing zero's, don't need them.
    
    float frac = llFabs(input);//we put the negative back at the end.
    string mantissa = (string)frac;//this may be a string of about 47 characters long.
    integer exponent = -6;//default exponent for optical method
    if(frac == (float)mantissa){
        mantissa = llDeleteSubString(mantissa, -7, -7);//remove the decimal point
        jump optical;
    }
    
    //optical failed
    //Ugly Math version; ugly in the sense that it is slow and not as elegant as working with it as a string.
    //A) calculate the exponent via approximation of C log2()
    //B) use kludge to avert fatal error in approximation of log2 result (only a problem with values >= 0x1.FFFFF8p127)
    //       the exponent is sometimes reported as 128, which will bork float math, so we subtract the test for 128.
    //       max_float btw is 0x1.FFFFFEp127, so we are only talking a very small number of numbers.
    //       but max_double is much larger but we aren't supporting those, we don't have the precision.
    //C) normalize the float with questionable exponent
    //D) calculate rounding error left from log2 approximation and add to normalization value.
    //       the '|' acts like a '+' in this instance but saves us one byte.
    
    if((exponent = llFloor(llLog(frac) / 0.69314718055994530941723212145818)) >= 128)//A
        exponent = ~-exponent;//B
    frac /= llPow(2.0, exponent);//C
    integer position = (24 | (integer)(3 <= frac)) - (integer)(frac); //D
    
    //this pushes the float into the integer buffer exactly.
    //since the shift is within integer range, we don't need to make a float.
    integer int = (integer)(frac * (1 << position));
    integer target = (integer)(frac = 0.0);//since the float is in the integer buffer, we need to clear the float buffer.
    
    //we don't use a traditional while loop, and instead opt for a do-while, because it's faster
    //since we may have to do about 128 iteration, this savings is important,
    //the exponent needs one final adjustment because of the shift, we do it here to save memory & it's faster.
    
    //The two loops try to make exponent == position by shifting and multiplying.
    //when they are equal, then this should be true ((int * llPow(10, exponent)) == llFabs(input))
    //That is of course assuming that the llPow(10, exponent) result has enough precision.
    
    //We recycle position for these loops as a temporary buffer. This is so we can save a few operations.
    //If we didn't, then we could actually optimize the variable out of the code; though it would be slower.
    if(target > (exponent -= position))
    {//apply the rest of the bit shift if |input| < 1
        do
        {
            if(int < 0x19999999)//(0x80000000 / 5)
            {//won't overflow, multiply in 5
                int = int * 5 + (position = (integer)(frac *= 5.0));
                frac -= (float)position;
                target = ~-target;
            }
            else
            {//overflow predicted, divide by 2
                frac = (frac + (int & 1))/2;
                int = int >> 1;
                exponent = -~exponent;
            }
        }while(target ^ exponent);
    }
    else if(target ^ exponent)//target < exponent
    {//apply the rest of the bit shift if |input| > 1
        do
        {
            if(int < 0x40000000) //(0x80000000 / 2)
            {//won't overflow, multiply in 2
                int = (int << 1) + (position = (integer)(frac *= 2.0));
                frac -= (float)position;
                exponent = ~-exponent;
            }
            else
            {//overflow predicted, divide by 5
                frac = (frac + int%5) / 5.0;
                int /= 5;
                target = -~target;
            }
        }while(target ^ exponent);
    }
    //int is now properly calculated, it holds enough data to accurately describe the input in conjunction with exponent.
    //we feed this through optical to clean up the answer.
    mantissa = (string)int;
    
    @optical;
    //it's not an issue that we may be jumping over the initialization of some of the variables,
    //we initialize everything we use here.
    
    //to accurately describe a float you only need 9 decimal places; so we throw the extras away
    
//    if(9 < (target = position = llStringLength(mantissa)))
//        position = 9;
    target = position = llStringLength(mantissa);
    
    //chop off the tailing zero's; we don't need them.
    do
        position = ~-position;
    while((llGetSubString(mantissa, position, position) == "0") && !!position);//faster then a while loop
    
    //we do a bad thing, we recycle 'target' here, position is one less then target,
    //"target + ~position" is the same as "target - (position + 1)" saves 6 bytes.
    //this block of code actually does the cutting.
    if(target + ~position) mantissa = llGetSubString(mantissa, 0, position);
    
    //insert the decimal point (not strictly needed). We add the extra zero for aesthetics.
    //by adding in the decimal point, it simplifies some of the code.
    mantissa = llInsertString(mantissa, 1, llGetSubString(".0", 0, !position));

    //adjust exponent from having added the decimal place
    if((exponent += ~-target))
        mantissa += "e" + (string)exponent;
    //return with the correct sign.
    if(input < 0)
        return "-" + mantissa;
    return mantissa;
}

Testbed

test(float a){
    string b = Float2Sci(a);
    llSay(0, llList2CSV([a, (float)b, b, a - (float)b, a == (float)b]));
}

default
{
    state_entry()
    {
        llListen(1, "", llGetOwner(), "");
        test(84932901.0);
        test(1.0);
        test(10.0);
    }
    listen(integer channel, string name, key id, string message)
    {
        test((float)message);
    }
}