MantisBT - opensim
View Issue Details
0008311opensim[REGION] Script Functionspublic2018-04-03 10:272019-02-06 11:28
Kayaker Magic 
Linux/MonoOpenSim 0.9.1.dev0.9.1 dev
master (dev code) 
Grid (Multiple Regions per Sim)
Mono / Linux64
0008311: llAcos and llAsin return NaN for values very close to 1.0
I've been seeing a very rare error where llAcos returns a bad value that turns out to be NaN. This is the value it returns when you give it an input value that is out of bounds. But this was happening to me while using llAcos to calculate the angle between two unit vectors, a situation that was never supposed to be out of bounds.

Floating point numbers, even doubles, have a finite resolution. If you generated unit vectors at all possible angles, each component of the vector would be truncated by a different amount. The result is that some of the unit vectors will be slightly longer than 1.0 (about 2% of them), some less than 1.0 (about 34%) and most actually would have a length that was the floating point representation of 1.0 (about 62%, based on random samples).

In a small number of innocent calculations, the truncation error will result in unit vectors that are slightly longer than one and the dot product calculated will be slightly greater than one. This is an invalid value for cosine and if you call llAcos to find the angle, the result will be NaN.

A similar function in the same source fie, llAngleBetween, tests for >1.0 and assumes an angle of 0.0 in that case. This isn't the best solution for llAcos because you do want to detect truly invalid values.

So I propose testing in llAcos for a small range of values between 1.0 and 1.0000001, forgiving those invalid values and substituting an angle of 0.0. The truncation errors in single-precision vector lengths should always be less than 0.0000001. The same thing should be done with llAsin, although it will substitute +/-PI_BY_TWO. llAtan has no range limit and does not have this problem.
The following script gets NaN errors caused by floating point truncation of unit vectors. After applying the following patch, the errors are gone.

//The dot product of a unit vector with itself should always be 1.0
// never >1.0
integer times=0;
integer fails=0;
        times += 1;
                //generate a random unit vector
        vector a=llVecNorm(< llFrand(2.0)-1.0, llFrand(2.0)-1.0, llFrand(2.0)-1.0>);
        vector b=a;
        float cos=a*b; //the dot product of two vectors is the cosine of the angle between them
        float angle = llAcos(cos);
        if (angle!=angle) //this will be true if angle==NaN, according to IEEE
        { //why would llAcos return NaN? Because cos>1.000000000
            fails += 1;
            llOwnerSay("NaN! vector="+(string)a+ " failed "+(string)fails+" of "+(string)times+", "+(string)((integer)(100.0*(float)fails/(float)times))+"%");
From 356225736b2418b797fb71328f84288c98a233c2 Mon Sep 17 00:00:00 2001
From: Mike Higgins <>
Date: Tue, 3 Apr 2018 09:47:55 -0700
Subject: [PATCH] put a test in llAcos and llAsin to prevent NaN on values
 slightly above 1.0

 .../Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
index bac1468..47c2355 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
@@ -860,6 +860,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api

         public LSL_Vector llVecNorm(LSL_Vector v)
+//Console.WriteLine("Called llVecNorm =============================================");
             return LSL_Vector.Norm(v);
@@ -5571,12 +5572,20 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
         public LSL_Float llAcos(double val)
- return (double)Math.Acos(val);
+ const double TRIG_THRESHOLD = 0.0000001f; //Kayaker: truncation errors can result in values slightly above 1.0
+ if (Math.Abs(val) > 1f && (Math.Abs(val)-1f)<TRIG_THRESHOLD) //llAcos should forgive this and
+ return (double)0.0; //return 0 degrees as if this was +/-1.0
+ return (double)Math.Acos(val); //else return the normal value

         public LSL_Float llAsin(double val)
+ const double TRIG_THRESHOLD = 0.0000001f; //Kayaker: truncation errors can result in values slighty above 1.0
+ if (val > 1f && (val-1f)<TRIG_THRESHOLD) //llAsin should forgive this and
+ return (double)ScriptBaseClass.PI_BY_TWO; //return pi/2 as if this was 1.0
+ if (val < -1f && (-val-1f)<TRIG_THRESHOLD)
+ return (double)-ScriptBaseClass.PI_BY_TWO; //or -pi/2 if this was -1.0
             return (double)Math.Asin(val);

No tags attached.
Issue History
2018-04-03 10:27Kayaker MagicNew Issue
2018-04-03 10:43melanieNote Added: 0032627
2018-04-03 11:05UbitUmarovNote Added: 0032628
2018-04-03 15:20UbitUmarovNote Added: 0032629
2018-04-03 17:23UbitUmarovNote Added: 0032630
2018-07-11 10:55UbitUmarovNote Added: 0032780
2018-07-11 10:55UbitUmarovStatusnew => resolved
2018-07-11 10:55UbitUmarovResolutionopen => fixed
2018-07-11 10:55UbitUmarovAssigned To => UbitUmarov
2019-02-06 11:28BillBlightNote Added: 0034389
2019-02-06 11:28BillBlightStatusresolved => closed

2018-04-03 10:43   
Please attach the patch in mailbox format. You can create that format using git format-patch.
2018-04-03 11:05   
you can see [^]
for future.

Sorry, can't accpet this patch.
This a normal issue with limited precision math on cpus.

you need to take this in consideration on your scripts, just where it is needed.
and not only on trig functions.

you can also modify the algoritm you use to avoid this issues.
2018-04-03 15:20   
on branch httptests i did add a few auxiliar functions

float osVecMagSquare(vector a)
float osVecDistSquare(vector a, vector b)
   returns the square of norm of vector or distance vector (a-b(, when expensive sqrt is not needed.

float osAngleBetween(vector a, vector b)
   returns the angle between a and b in radian -pi <= angle <= pi.

all this could be done on lsl, but for completeness..

float osRound(LSL_Float value, LSL_Integer ndigits)
   returns the value rounded to the number with a number if decimal places set by ndigits
   ndigits = 0 is same as llRound(), max value is 15
   note that ll*Say do llRound(value,6) by default so you can't test with them.

this functions operate with doubles in current implementation
but keep in mind that most operations read/write from/to floats on the framework, for example prim positions.

let me know what i did mess up ;)
2018-04-03 17:23   
float osAngleBetween(vector a, vector b) returns a angle between 0 and PI not -pi to pi
2018-07-11 10:55   
alternative solution using new auxiliar ossl
2019-02-06 11:28   
Marked as Resolved but never closed, can be reopened if needed.