使用ULP比较双打(最后的位置)

时间:2014-01-24 19:02:04

标签: javascript c++ performance floating-point-precision emscripten

我成功编写了一个基于Ulps的函数,它将两个双精度数相等。根据{{​​3}},可以使用绝对和相对epsilon的组合或使用整数(Ulps)进行比较。

我已经完成了基于epsilon和基于Ulps的功能。这是基于epsilon的功能:

var IsAlmostEqual_Epsilon = function(a, b)
{
  if (a == b) return true;
  var diff = Math.abs(a - b);
  if (diff < 4.94065645841247E-320) return true;
  a = Math.abs(a);
  b = Math.abs(b);
  var smallest = (b < a) ? b : a;
  return diff < smallest * 1e-12;
}

这是基于Ulps的(DoubleToInt64Bitssubtractnegatelessthan函数位于下面提到的JSBIN中:

var IsAlmostEqual_Ulps = function(A, B)
{
  if (A==B) return true;
  DoubleToInt64Bits(A, aInt);
  if(aInt.hi < 0) aInt = subtract(Int64_MinValue, aInt);
  DoubleToInt64Bits(B, bInt);
  if(bInt.hi < 0) bInt = subtract(Int64_MinValue, bInt);
  var sub = subtract(aInt, bInt);
  if (sub.hi < 0) sub = negate(sub);
  if (lessthan(sub, maxUlps)) return true;
  return false;
}

根据this page,基于Ulps是首选。根据83个案例的测试基础IsAlmostEqual_Ulps正常工作,但功能很慢。当作为独立的html(在JSBIN之外)执行时,完成测试基础(Bruce Dawson)大约需要700-900毫秒。基于Epsilon的IsAlmostEqual_Epsilon只需要大约100毫秒。

有什么方法可以加速IsAlmostEqual_Ulps功能吗?您可以为我的代码提出一个完全不同的解决方案或一些修正。

我已经测试了所有内容,但它只减少了5-10%的时间。我正在寻找像执行时间改善50-80%的东西。 100-500%的改善会很好,但这可能只是一个梦想。

JSBIN代码中的

right_answers使用C#IsAlmostEqual函数(参见JSBIN代码的顶部)。上述两个函数在所有83个案例中都给出了相同的结果。


编辑:

来自JSBIN的C ++版本:

bool IsAlmostEqual(double A, double B)
{
    //http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
    long long aInt = reinterpret_cast<long long&>(A);
    if (aInt < 0) aInt = -9223372036854775808LL - aInt;
    long long bInt = reinterpret_cast<long long&>(B);
    if (bInt < 0) bInt = -9223372036854775808LL - bInt;
    return (std::abs(aInt - bInt) <= 10000);
}

3 个答案:

答案 0 :(得分:2)

有一种方法肯定会更快(目前速度是本机代码速度的1.5倍),这将是使用asm.js,这是一个高度可优化的Javascript子集。

这是运行它的unreal gaming engine,以了解性能类型(需要加载但运行非常流畅,使用firefox或chrome)。

它的工作方式是你接受代码并用静态类型语言编写:C或C ++。然后将其编译为LLVM C ++虚拟机的字节码。然后使用emscripten编译器生成asm.js Javascript代码。

如果浏览器未检测并优化asm.js,则将其作为Javascript运行。如果浏览器(如最新版本的Chrome / Firefox)检测到asm.js,您将获得近乎原生的性能。

检查John Resig的blog post(jQuery的创建者)。这些天你不能比浏览器中的速度更快,而且速度也越来越快(参见Mozilla团队博客文章here)。

答案 1 :(得分:2)

以下是如何使用类型化数组的新浏览器功能来提高性能的基本要点。请注意,由于未提供本机64位int视图,因此您必须基于2个32位整数自行重写逻辑,并确保字节顺序不会影响不同系统上的计算。我只是想解决问题的javascript速度部分,而不是浮点编码的专家。希望这很清楚:

var IsAlmostEqual_Ulps = function(A, B)
{
  var smallBufferA = new ArrayBuffer(8); //8 bytes = 64bits
  var viewAsDoubleA = new Float64Array(smallBufferA); //byteOffset=0, length=8 optional
  var viewAsIntA = new Int32Array(smallBufferA); //byteOffset=0, length=8 optional
  viewAsDoubleA.set([A]);
  console.log(viewAsIntA);
  var smallBufferB = new ArrayBuffer(8); //8 bytes = 64bits
  var viewAsDoubleB = new Float64Array(smallBufferB); //byteOffset=0, length=8 optional
  var viewAsIntB = new Int32Array(smallBufferB); //byteOffset=0, length=8 optional
  viewAsDoubleB.set([B]);
  console.log(viewAsIntB);
  //The logic below is just illustrative and may not be right. Please test & adjust
  var A_lo=viewAsIntA[1];
  var B_lo=viewAsIntB[1];
  var A_hi=viewAsIntA[0];
  var B_hi=viewAsIntB[0];
  if(A_hi<0){A_hi=Int32_MinValue-A_hi;}
  if(A_hi<0){A_hi=Int32_MinValue-A_hi;}
  //You'll have to rewrite these slightly, I think you know how
  var sub = subtract(viewAsIntA, viewAsIntB); 
  if (sub.hi < 0) sub = negate(sub);
  if (lessthan(sub, maxUlps)) return true;
}

例如,如果您调用IsAlmostEqual_Ulps(3.14159,3.14159001),则会在控制台中登录:

[-266631570, 1074340345]
[-244113572, 1074340345]

这应该会让你获得最大的性能提升。如果你想让它更快,你应该在函数调用之间重用现有的缓冲区(来自全局范围或闭包),而不是招致构造函数开销和垃圾收集。

答案 2 :(得分:1)

好吧,既然我原来的“见解”并没有太大帮助,那么这是另一个答案,它只是简单地使用现有的函数并重新组织它们以最小化重复的对象构造函数调用:

var ULPS2 = function(){
  var buf2 = new ArrayBuffer(8);
  var dataView2A = new DataView(buf2);
  var dataView2B = new DataView(buf2);
  var aInt=new Int64(0, 0), bInt=new Int64(0, 0);
  var sub;

  this.IsAlmostEqual=function(A,B){
    if (A==B) return true;
    dataView2A.setFloat64(0, A);
    aInt.lo = dataView2A.getInt32(4) | 0;
    aInt.hi = dataView2A.getInt32(0) | 0;
    dataView2B.setFloat64(0, B);
    bInt.lo = dataView2B.getInt32(4) | 0;
    bInt.hi = dataView2B.getInt32(0) | 0;

    if(aInt.hi < 0) aInt = subtract(Int64_MinValue, aInt);

    if(bInt.hi < 0) bInt = subtract(Int64_MinValue, bInt);
    sub = subtract(aInt, bInt);
    if (sub.hi < 0) sub = negate(sub);
    if (lessthan(sub, maxUlps)) return true;
    return false;
  }

}
var Ulps2=new ULPS2();

根据http://jsbin.com/IWoyIDO/2/在Chrome和Firefox中的测试,它似乎可以提高30%-50%。不是惊人的,但至少比你原先提到的5-10%的改善要好。