跨浏览器JavaScript数字精度

在JavaScript中,数字被定义为64位双精度。 对于分布式Web应用程序,我有一个特定的使用方法,只有在所有浏览器都可以依赖一致的结果时才能使用。

尽pipe使用IEEE标准的规范,我自然怀疑math库甚至底层硬件的实现可能会有细微的差异,这可能会导致复合错误。

是否有任何兼容性数据的来源,或可靠的testing套件来validation浏览器中的双精度计算? 特别是,我还需要考虑移动浏览器(通常是基于ARM)。

澄清 –

这是关于浏览器兼容性的问题。 我试图了解是否所有的浏览器都可以依赖于以一种可靠,一致和可重复的方式来处理IEEE浮点数。 在大多数语言中,这是一个安全的假设,但有趣的是浏览器中对此有一些不确定性。

关于如何避免由于精度不足和舍入错误而导致的浮点问题,有一些很好的build议。 在大多数情况下,如果您需要准确性,您应该遵循这个build议!

对于这个问题,我并不是想避免这个问题,而是去理解它。 浮点数在devise上本质上是不准确的,但只要注意如何build立不准确性是完全可预测和一致的。 IEEE-754将其描述为只有一个标准组织可以进行的详细程度。

我决定提供一个小赏金,如果有人可以引用,

  • 真正的兼容性数据与在主stream浏览器中实现IEEE编号有关。
  • 一个旨在validation浏览器内实现的testing套件,包括validation64位浮点数(53位尾数)的正确内部使用情况。

在这个问题上,我不寻找替代select,解决方法或方法来避免这个问题。 感谢您的build议。

(编辑:下面提到的错误是closures的固定在2016年3月3日。所以我现在的答案是“也许”。)

不幸的是,答案是否定的。 在v8中至less有一个突出的bug,由于双舍入,意味着它可能不符合32位Linux上的IEEE 754双精度。

  • V8的错误条目
  • 火狐入门 – 2009年修复
  • 关于双舍入问题的更多信息

这可以用以下方法testing:

 9007199254740994 + 0.99999 === 9007199254740994 

我可以在32位Ubuntu上运行的Chrome 26.0.1410.63上validation这是否失败(左侧是9007199254740996 )。 它传递在同一个系统上的Firefox 20.0。 至less,这个testing应该被添加到你的testing套件,也许test262。

这只是为了好玩,正如你已经说过的,我创造了一个新的答案,因为这是一个不同的脉络。 但是我仍然觉得有几个随便的路人会忽略这个问题的无用性。 所以,让我们从解决你的观点开始:

首先:

真正的兼容性数据与在主stream浏览器中实现IEEE编号有关。

不存在,就此而言甚至没有任何意义, IEEE只是一个标准组织…? 我不确定这个模糊的故意或者意外,我会假设你正在试图说IEEE 754,但是存在着这个问题…这个标准在技术上有两个版本IEEE 754-2008和IEEE 754-1985 。 基本上前者是较新的,并解决后者的疏忽。 任何有理智的人都会认为,任何维护的JavaScript实现都会更新到最新,最好的标准,但是任何理智的人都应该比JavaScript更懂JavaScript,即使JavaScript不疯狂,也没有规范说实现必须是/保持最新( 如果您不相信我 , 请查看ECMA规范 ,他们甚至不会说“版本”)。 为了使事情进一步复杂化,浮点运算IEEE标准754-2008支持两种编码格式:十进制编码格式和二进制编码格式。 正如我们所期望的那样,它们是相互兼容的,因为您可以在不丢失数据的情况下来回访问数据,但是假设我们可以访问数字的二进制表示( 我们不需要) (没有附加debugging器和看着老店的方式)

然而,从我可以告诉它看来,通常的做法是用一个旧式的“ double ”来“回”一个JavaScript Number ,这当然意味着我们处在用于实际构build浏览器的编译器的摆布之中。 但是即使在这个领域,即使所有的编译器都是在同一个版本的标准下,我们也不能也不应该假设它们是平等的,即使所有的编译器都完全实现了标准别)。 这里是本文的摘录,我认为这是一个有趣的,值得的和相关的对这个对话阅读…

许多程序员喜欢相信他们可以理解程序的行为,并且certificate它将正确地工作,而无需参考编译它的编译器或运行它的计算机。 在许多方面,支持这种信念是计算机系统和编程语言devise者的一个有价值的目标。 不幸的是,当涉及到浮点算术时,目标实际上是不可能实现的。 IEEE标准的作者知道,他们并没有试图实现它。 因此,尽pipe整个计算机产业几乎普遍符合IEEE 754标准(大部分),但便携式软件的程序员必须继续应对不可预知的浮点algorithm。

当发现我也发现这个参考实现在JavaScript中完全完成 (注意:我没有实际validation实现的有效性)。

所有这一切,让我们继续你的第二个要求:

一个旨在validation浏览器内实现的testing套件,包括validation64位浮点数(53位尾数)的正确内部使用情况。

由于JavaScript是一个解释的平台,现在你应该看到,从JavaScript的angular度来看,没有办法testing编译器+机器脚本+编译器(VM /引擎)+编译器的绝对可靠的方式。 所以,除非你想build立一个testing套件,作为一个浏览器主机,并且实际上“偷窥”进程的私有内存,以确保一个有效的表示,而这最终可能是徒劳无功的,因为这个数字很可能被“支持”一个double ,这将符合,因为它在内置的浏览器的C或C + +。没有绝对的方式来从JavaScript做到这一点,因为我们所有的访问是“对象”,即使当我们查看Number在控制台中,我们正在看.toString版本。 对于这个问题,我会认为这是唯一的forms,因为它将从二进制确定,只会成为一个失败点,如果对于该语句: n1 === n2 && n1.toString() !== n2.toString()你可以find一个相关的n1, n2

也就是说,我们可以testingstring版本,实际上,只要我们记住一些古怪的东西,就和testing二进制文件一样好。 特别是因为JavaScript引擎/虚拟机之外没有任何东西触及二进制版本。 然而,这使你摆脱了一个奇怪的特定的,可能非常挑剔,并有望被改变的失败点的摆布。 仅供参考,以下是webkit的JavaScriptCore的Number Prototype(NumberPrototype.cpp)的摘录,显示了转换的复杂性:

  // The largest finite floating point number is 1.mantissa * 2^(0x7fe-0x3ff). // Since 2^N in binary is a one bit followed by N zero bits. 1 * 2^3ff requires // at most 1024 characters to the left of a decimal point, in base 2 (1025 if // we include a minus sign). For the fraction, a value with an exponent of 0 // has up to 52 bits to the right of the decimal point. Each decrement of the // exponent down to a minimum of -0x3fe adds an additional digit to the length // of the fraction. As such the maximum fraction size is 1075 (1076 including // a point). We pick a buffer size such that can simply place the point in the // center of the buffer, and are guaranteed to have enough space in each direction // fo any number of digits an IEEE number may require to represent. typedef char RadixBuffer[2180]; // Mapping from integers 0..35 to digit identifying this value, for radix 2..36. static const char* const radixDigits = "0123456789abcdefghijklmnopqrstuvwxyz"; static char* toStringWithRadix(RadixBuffer& buffer, double number, unsigned radix) { ASSERT(isfinite(number)); ASSERT(radix >= 2 && radix <= 36); // Position the decimal point at the center of the string, set // the startOfResultString pointer to point at the decimal point. char* decimalPoint = buffer + sizeof(buffer) / 2; char* startOfResultString = decimalPoint; // Extract the sign. bool isNegative = number < 0; if (signbit(number)) number = -number; double integerPart = floor(number); // We use this to test for odd values in odd radix bases. // Where the base is even, (eg 10), to determine whether a value is even we need only // consider the least significant digit. For example, 124 in base 10 is even, because '4' // is even. if the radix is odd, then the radix raised to an integer power is also odd. // Eg in base 5, 124 represents (1 * 125 + 2 * 25 + 4 * 5). Since each digit in the value // is multiplied by an odd number, the result is even if the sum of all digits is even. // // For the integer portion of the result, we only need test whether the integer value is // even or odd. For each digit of the fraction added, we should invert our idea of whether // the number is odd if the new digit is odd. // // Also initialize digit to this value; for even radix values we only need track whether // the last individual digit was odd. bool integerPartIsOdd = integerPart <= static_cast<double>(0x1FFFFFFFFFFFFFull) && static_cast<int64_t>(integerPart) & 1; ASSERT(integerPartIsOdd == static_cast<bool>(fmod(integerPart, 2))); bool isOddInOddRadix = integerPartIsOdd; uint32_t digit = integerPartIsOdd; // Check if the value has a fractional part to convert. double fractionPart = number - integerPart; if (fractionPart) { // Write the decimal point now. *decimalPoint = '.'; // Higher precision representation of the fractional part. Uint16WithFraction fraction(fractionPart); bool needsRoundingUp = false; char* endOfResultString = decimalPoint + 1; // Calculate the delta from the current number to the next & previous possible IEEE numbers. double nextNumber = nextafter(number, std::numeric_limits<double>::infinity()); double lastNumber = nextafter(number, -std::numeric_limits<double>::infinity()); ASSERT(isfinite(nextNumber) && !signbit(nextNumber)); ASSERT(isfinite(lastNumber) && !signbit(lastNumber)); double deltaNextDouble = nextNumber - number; double deltaLastDouble = number - lastNumber; ASSERT(isfinite(deltaNextDouble) && !signbit(deltaNextDouble)); ASSERT(isfinite(deltaLastDouble) && !signbit(deltaLastDouble)); // We track the delta from the current value to the next, to track how many digits of the // fraction we need to write. For example, if the value we are converting is precisely // 1.2345, so far we have written the digits "1.23" to a string leaving a remainder of // 0.45, and we want to determine whether we can round off, or whether we need to keep // appending digits ('4'). We can stop adding digits provided that then next possible // lower IEEE value is further from 1.23 than the remainder we'd be rounding off (0.45), // which is to say, less than 1.2255. Put another way, the delta between the prior // possible value and this number must be more than 2x the remainder we'd be rounding off // (or more simply half the delta between numbers must be greater than the remainder). // // Similarly we need track the delta to the next possible value, to dertermine whether // to round up. In almost all cases (other than at exponent boundaries) the deltas to // prior and subsequent values are identical, so we don't need track then separately. if (deltaNextDouble != deltaLastDouble) { // Since the deltas are different track them separately. Pre-multiply by 0.5. Uint16WithFraction halfDeltaNext(deltaNextDouble, 1); Uint16WithFraction halfDeltaLast(deltaLastDouble, 1); while (true) { // examine the remainder to determine whether we should be considering rounding // up or down. If remainder is precisely 0.5 rounding is to even. int dComparePoint5 = fraction.comparePoint5(); if (dComparePoint5 > 0 || (!dComparePoint5 && (radix & 1 ? isOddInOddRadix : digit & 1))) { // Check for rounding up; are we closer to the value we'd round off to than // the next IEEE value would be? if (fraction.sumGreaterThanOne(halfDeltaNext)) { needsRoundingUp = true; break; } } else { // Check for rounding down; are we closer to the value we'd round off to than // the prior IEEE value would be? if (fraction < halfDeltaLast) break; } ASSERT(endOfResultString < (buffer + sizeof(buffer) - 1)); // Write a digit to the string. fraction *= radix; digit = fraction.floorAndSubtract(); *endOfResultString++ = radixDigits[digit]; // Keep track whether the portion written is currently even, if the radix is odd. if (digit & 1) isOddInOddRadix = !isOddInOddRadix; // Shift the fractions by radix. halfDeltaNext *= radix; halfDeltaLast *= radix; } } else { // This code is identical to that above, except since deltaNextDouble != deltaLastDouble // we don't need to track these two values separately. Uint16WithFraction halfDelta(deltaNextDouble, 1); while (true) { int dComparePoint5 = fraction.comparePoint5(); if (dComparePoint5 > 0 || (!dComparePoint5 && (radix & 1 ? isOddInOddRadix : digit & 1))) { if (fraction.sumGreaterThanOne(halfDelta)) { needsRoundingUp = true; break; } } else if (fraction < halfDelta) break; ASSERT(endOfResultString < (buffer + sizeof(buffer) - 1)); fraction *= radix; digit = fraction.floorAndSubtract(); if (digit & 1) isOddInOddRadix = !isOddInOddRadix; *endOfResultString++ = radixDigits[digit]; halfDelta *= radix; } } // Check if the fraction needs rounding off (flag set in the loop writing digits, above). if (needsRoundingUp) { // Whilst the last digit is the maximum in the current radix, remove it. // eg rounding up the last digit in "12.3999" is the same as rounding up the // last digit in "12.3" - both round up to "12.4". while (endOfResultString[-1] == radixDigits[radix - 1]) --endOfResultString; // Radix digits are sequential in ascii/unicode, except for '9' and 'a'. // Eg the first 'if' case handles rounding 67.89 to 67.8a in base 16. // The 'else if' case handles rounding of all other digits. if (endOfResultString[-1] == '9') endOfResultString[-1] = 'a'; else if (endOfResultString[-1] != '.') ++endOfResultString[-1]; else { // One other possibility - there may be no digits to round up in the fraction // (or all may be been rounded off already), in which case we may need to // round into the integer portion of the number. Remove the decimal point. --endOfResultString; // In order to get here there must have been a non-zero fraction, in which case // there must be at least one bit of the value's mantissa not in use in the // integer part of the number. As such, adding to the integer part should not // be able to lose precision. ASSERT((integerPart + 1) - integerPart == 1); ++integerPart; } } else { // We only need to check for trailing zeros if the value does not get rounded up. while (endOfResultString[-1] == '0') --endOfResultString; } *endOfResultString = '\0'; ASSERT(endOfResultString < buffer + sizeof(buffer)); } else *decimalPoint = '\0'; BigInteger units(integerPart); // Always loop at least once, to emit at least '0'. do { ASSERT(buffer < startOfResultString); // Read a single digit and write it to the front of the string. // Divide by radix to remove one digit from the value. digit = units.divide(radix); *--startOfResultString = radixDigits[digit]; } while (!!units); // If the number is negative, prepend '-'. if (isNegative) *--startOfResultString = '-'; ASSERT(buffer <= startOfResultString); return startOfResultString; } 

…你可以看到,这里的数字是由一个传统的double支持,转换是简单而直接的。 所以我devise的是这样的:因为我猜想这些实现将不同的唯一的地方是它们对string的“渲染”。 我build立了一个三重的testing生成器:

  1. 根据参考string结果testing“string结果”
  2. testing他们的parsing等价物(忽略任何epsilon,我的意思是确切的!)
  3. testing一个特殊版本的string,只是调整四舍五入“解释”

为了实现这一点,我们需要访问一个参考构build,我的第一个想法是使用本地语言的一个,但我发现生成的数字似乎比JavaScript更精确,导致更多的错误。 那么我想,如果我只是使用已经在JavaScript引擎中的实现。 WebKit / JavaScriptCore似乎是一个非常不错的select,但是如果让引用的构build和运行也会有很多工作,所以我select了.NET的简单性,因为它可以访问“jScript”,而初始时并不理想考试比本地对手产生更接近的结果。 我真的不想在jScript中编写代码,因为语言几乎不赞成使用,所以我select了C#通过CodeDomProvider引导jScript ….稍微修改一下这里是它生成的: http : //jsbin.com/afiqil (最后演示酱!!!! 1!) ,所以现在你可以运行它在所有的浏览器和编译自己的数据,这在我个人的检查,似乎string四舍五入解释各不相同的浏览器我试过了,但我还没有find一个主要的浏览器,处理幕后的数字(其他的string化)不同…

现在为C#酱油:

  using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.CodeDom.Compiler; using System.Reflection; namespace DoubleFloatJs { public partial class Form1 : Form { private static string preamble = @" var successes = []; var failures = []; function fpu_test_add(v1, v2) { return '' + (v1 + v2); } function fpu_test_sub(v1, v2) { return '' + (v1 - v2); } function fpu_test_mul(v1, v2) { return '' + (v1 * v2); } function fpu_test_div(v1, v2) { return '' + (v1 / v2); } function format(name, result1, result2, result3, received, expected) { return '<span style=""display:inline-block;width:350px;"">' + name + '</span>' + '<span style=""display:inline-block;width:60px;text-align:center;font-weight:bold; color:' + (result1 ? 'green;"">OK' : 'red;"">NO') + '</span>' + '<span style=""display:inline-block;width:60px;text-align:center;font-weight:bold; color:' + (result2 ? 'green;"">OK' : 'red;"">NO') + '</span>' + '<span style=""display:inline-block;width:60px;text-align:center;font-weight:bold; color:' + (result3 ? 'green;"">OK' : 'red;"">NO') + '</span>' + '<span style=""display:inline-block;width:200px;vertical-align:top;"">' + received + '<br />' + expected + '</span>'; } function check_ignore_round(received, expected) { return received.length > 8 && received.length == expected.length && received.substr(0, received.length - 1) === expected.substr(0, expected.length - 1); } function check_parse_parity_no_epsilon(received, expected) { return parseFloat(received) === parseFloat(expected); } function fpu_test_result(v1, v2, textFn, received, expected) { var result = expected === received, resultNoRound = check_ignore_round(received, expected), resultParse = check_parse_parity_no_epsilon(received, expected), resDiv = document.createElement('div'); resDiv.style.whiteSpace = 'nowrap'; resDiv.style.fontFamily = 'Courier New, Courier, monospace'; resDiv.style.fontSize = '0.74em'; resDiv.style.background = result ? '#aaffaa' : '#ffaaaa'; resDiv.style.borderBottom = 'solid 1px #696969'; resDiv.style.padding = '2px'; resDiv.innerHTML = format(textFn + '(' + v1 + ', ' + v2 + ')', result, resultNoRound, resultParse, received, expected); document.body.appendChild(resDiv); (result ? successes : failures).push(resDiv); return resDiv; } function fpu_test_run(v1, v2, addRes, subRes, mulRes, divRes) { var i, res, fnLst = [fpu_test_add, fpu_test_sub, fpu_test_mul, fpu_test_div], fnNam = ['add', 'sub', 'mul', 'div']; for (i = 0; i < fnLst.length; i++) { res = fnLst[i].call(null, v1, v2); fpu_test_result(v1, v2, fnNam[i], res, arguments[i + 2]); } } function setDisplay(s, f) { var i; for (i = 0; i < successes.length; i++) { successes[i].style.display = s; } for (i = 0; i < failures.length; i++) { failures[i].style.display = f; } } var test_header = fpu_test_result('value1', 'value2', 'func', 'received', 'expected'), test_header_cols = test_header.getElementsByTagName('span'); test_header_cols[1].innerHTML = 'string'; test_header_cols[2].innerHTML = 'rounded'; test_header_cols[3].innerHTML = 'parsed'; test_header.style.background = '#aaaaff'; failures.length = successes.length = 0; "; private static string summation = @" var bs = document.createElement('button'); var bf = document.createElement('button'); var ba = document.createElement('button'); bs.innerHTML = 'show successes (' + successes.length + ')'; bf.innerHTML = 'show failures (' + failures.length + ')'; ba.innerHTML = 'show all (' + (successes.length + failures.length) + ')'; ba.style.width = bs.style.width = bf.style.width = '200px'; ba.style.margin = bs.style.margin = bf.style.margin = '4px'; ba.style.padding = bs.style.padding = bf.style.padding = '4px'; bs.onclick = function() { setDisplay('block', 'none'); }; bf.onclick = function() { setDisplay('none', 'block'); }; ba.onclick = function() { setDisplay('block', 'block'); }; document.body.insertBefore(bs, test_header); document.body.insertBefore(bf, test_header); document.body.insertBefore(ba, test_header); document.body.style.minWidth = '700px'; "; private void buttonGenerate_Click(object sender, EventArgs e) { var numberOfTests = this.numericNumOfTests.Value; var strb = new StringBuilder(preamble); var rand = new Random(); for (int i = 0; i < numberOfTests; i++) { double v1 = rand.NextDouble(); double v2 = rand.NextDouble(); strb.Append("fpu_test_run(") .Append(v1) .Append(", ") .Append(v2) .Append(", '") .Append(JsEval("" + v1 + '+' + v2)) .Append("', '") .Append(JsEval("" + v1 + '-' + v2)) .Append("', '") .Append(JsEval("" + v1 + '*' + v2)) .Append("', '") .Append(JsEval("" + v1 + '/' + v2)) .Append("');") .AppendLine(); } strb.Append(summation); this.textboxOutput.Text = strb.ToString(); Clipboard.SetText(this.textboxOutput.Text); } public Form1() { InitializeComponent(); Type evalType = CodeDomProvider .CreateProvider("JScript") .CompileAssemblyFromSource(new CompilerParameters(), "package e{class v{public static function e(e:String):String{return eval(e);}}}") .CompiledAssembly .GetType("ev"); this.JsEval = s => (string)evalType.GetMethod("e").Invoke(null, new[] { s }); } private readonly Func<string, string> JsEval; } } 

或预编译的版本,如果您应该select: http : //uploading.com/files/ad4a85md/DoubleFloatJs.exe/ 这是一个可执行文件,下载需自行承担风险

测试发生器应用程序屏幕截图

我应该提到,该程序的目的只是在文本框中生成一个JavaScript文件,并将其复制到剪贴板以方便粘贴,无论您select哪个位置,都可以轻松地将其转到asp.net服务器上,添加报告结果ping服务器,并跟踪在一些海量数据库…这是我会做的,如果我想要的信息..

…和,…我,我花了,我希望这可以帮助你-ck

总结下面的一切,你可以期望在大多数系统上遵守一些IE的小故障,但是应该使用完整性检查作为预防措施(包括命题)。

  • ECMAScript规范 (包括版本3和版本5)在IEEE 754的特性(如转换,舍入模式,上溢/下溢甚至带符号的零)方面非常精确(第5.2,8.5,9。,11.5。*,11.8.5; 15.7和15.8也处理漂浮)。 它不只是“把事情留给底层的实现”。 vv之间没有明显的区别。 3和5, 所有主stream浏览器至less支持v.3 。 因此,它的规则至less在名义上得到了所有人的尊重。 让我们来看看…

    • 没有浏览器完全通过test262 ECMAScript一致性testing ( WP#一致性testing )。 但是, 谷歌没有发现 test262错误是浮动相关的。

    • IE5.5 +( [MS-ES3] )报告Number.toFixedNumber.toExponentialNumber.toPrecision 。 其他差异不是浮动相关的。 我无法在IE8中运行test262;

    • FF使用特殊types的数字和它们之间的强制转换fns和Ctypes(见​​例如JSAPI用户指南 ),这表明事实上并不是传给C语言的。FF10的testing没有显示任何与浮点有关的错误;
    • Opera用户在半年前曾报告与浮动相关的错误 ;
    • Webkit的test262失败看起来不是浮动相关的 。

为了validation一个系统,你可以使用test262中与浮点相关的testing。 他们位于http://test262.ecmascript.org/json/ch<2-digit # of spec chapter>.json ; testing代码可以用(python 2.6+)提取:

 ch="05"; #substitute chapter # import urllib,json,base64 j=json.load(urllib.urlopen("http://test262.ecmascript.org/json/ch%s.json"%ch)) tt=j['testsCollection']['tests'] f=open('ch%s.js'%ch,'w') for t in tt: print >>f print >>f,base64.b64decode(t['code']) f.close() 

另一个机会是C中的IEEE 754一致性testing 。

test262的相关部分(比较浮点数的部分)如下:

 { "S11": "5.1.A4: T1-T8", "S15": { "7": "3: 2.A1 & 3.A1", "8": { "1": "1-8: A1", "2": { "4": "A4 & A5", "5": "A: 3,6,7,10-13,15,17-19", "7": "A6 & A7", "13": "A24", "16": "A6 & A7", "17": "A6", "18": "A6" } } }, "S8": "5.A2: 1 & 2" } 

这个列表以及所有相关testing文件的连接源(截至2012年3月9日,线束中没有任何文件)可以在这里find: http ://pastebin.com/U6nX6sKL

一般的经验法则是,当数字精度很重要,而且只能访问浮点精度数字时,所有的计算都应该用整数运算来保证有效性(确保有效数据的15位数字)。 是的,在JavaScript中有一些通用的数字特质,但是它们更多地与浮点数中的精度不足有关,而与标准的UA实现不相关。 四处寻找浮点math的陷阱,数量众多,诡计多端。

我觉得应该稍微详细一点,比如我写了一个程序(用JavaScript),用基本的微积分来确定多边形的面积,单位是米或英尺。 这个程序并没有像现在这样进行计算,而是将所有的东西都转换成了微米,并且在那里进行了计算,因为所有东西都会更加完整。

希望这有助于-ck


回应你的澄清,评论和关注

我不打算在下面全部重复我的评论,但是简短的回答是没有人能够说100%的设备上的每个实现都是100%。 期。 我可以说和其他人会告诉你一样,是在目前的主stream浏览器,我没有看到也没有听说过任何浏览器涉及浮点数有害的bug。 但是你的问题本身就是一把双刃剑,因为你要“依靠”“不可靠的”结果 ,或者只是希望所有的浏览器都“一致地不一致” – 换句话说,不要试图确保一只狮子会玩取指,你的时间会更好地花在寻找一只狗,这意味着: 你可以依靠110%的整数math和上述math的结果 ,这同样适用于已经build议给你的stringmath…

祝你好运-ck

"I have a specific use in mind for a distributed web application, which would only work if I can rely on consistent results across all browsers."

Then the answer is no. You can not relay on a specification to tell you that a browser correctly handles floats. Chrome updates every 6 weeks, so even if you have the specifications Chrome could change there behavior in the next release.

You have to relay on feature testing that test your assumptions before each time before you calculations is run.

Maybe you should use a library for your calculations. For example bignumber has a good handling of floating point numbers. Here you should be save from environment changes because it uses it's own storage format.

This is a problem since ages in computing. And if you ask old programmers who matured from assembly language, they will tell you that you store important numbers in a different format and do manipulations on them in similar way too.

For example, a currency value can be saved as integer by multiplying the float value by 100 (to keep the 2 decimal places intact). You can then safely do calculations and when you have to display the final result, divide there by 100. Depending upon how many decimal places you have to keep secure and safe, you may have to select a different number other than 100. Store things in a long value and be care-free about such problems ever.

This is what gives me satisfactory results across platforms so far. I just keep myself away from the floating point arithmetic nuances this way