跨浏览器Javascript号精度

时间:2012-02-22 04:58:24

标签: javascript numbers

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

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

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

澄清 -

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

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

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

如果有人能引用,我决定提供小额赏金,

  • 与主流浏览器中IEEE号码实现相关的真正兼容性数据。
  • 用于验证浏览器内实现的测试套件,包括验证64位浮点数(53位尾数)的正确内部使用。

在这个问题中,我不是在寻找替代选项,解决方法或避免问题的方法。谢谢你的建议。

7 个答案:

答案 0 :(得分:19)

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

首先:

  

与IEEE实施相关的真实兼容性数据   主流浏览器中的数字。

不存在,就此而言甚至没有任何意义,IEEE只是一个标准组织......?我不确定这个模糊是故意的还是意外的,我会假设你试图说IEEE 754,但是存在谎言...技术上有2个版本的这个标准IEEE 754-2008和{{3 }}。基本上前者更新,并解决了后者的疏忽。任何理智的人都会认为任何维护的JavaScript实现都会更新到最新和最好的标准,但任何理智的人都应该比这更好地了解JavaScript,即使JavaScript并不疯狂,也没有规范说实现有要保持最新状态(IEEE 754-1985,他们甚至不会说话#34;版本")。为了使问题进一步复杂化,IEEE标准754-2008浮点运算支持两个 编码格式:十进制编码格式和二进制编码格式。在您可以来回移动而不丢失数据的意义上,可以预期哪些是相互兼容的,但是假设我们可以访问数字的二进制表示,我们不会# 39; t (没有附加调试器并以旧学校的方式查看商店)

但是,据我所知,似乎通常的做法是"返回"带有旧式Number的JavaScript double,这当然意味着我们受制于用于实际构建浏览器的编译器的支配。但即使在这个领域,即使所有的编译器都使用相同版本的标准(它们都不是),即使所有编译器都实现了这一点,我们也不能并且不应该假设相等。完全是标准(他们不是)。这是check the ECMA spec yourself if you don't believe me的摘录,我认为这是一个有趣的,有价值的,与此对话相关的阅读......

  

许多程序员喜欢相信他们能够理解这种行为   一个程序,并证明它将无法参考正常工作   编译它的编译器或运行它的计算机。在很多   支持这种信念的方式对于设计师来说是一个有价值的目标   计算机系统和编程语言。不幸的是,当它   来到浮点运算,目标几乎是不可能的   实现。 IEEE标准的作者知道这一点,以及他们   没有尝试实现它。因此,尽管几乎普遍   在整个计算机中符合(大部分)IEEE 754标准   业界,便携式软件的程序员必须继续应对   不可预测的浮点运算。

虽然发现我也发现了this paper(注意:我实际上并没有验证实施的有效性)。

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

  

一个测试套件,旨在验证其中的实现   浏览器,包括验证64位的正确内部使用   浮点数(53位尾数)。

由于JavaScript是一个解释平台,你现在应该看到没有办法测试编译编译器+机器的脚本+编译器(VM /引擎)+编译器的集合。从JavaScript的角度来看可靠的方式。因此,除非您想构建一个充当浏览器主机的测试套件,并且实际上是“偷看”#34;进入私人记忆的过程,以确保有效的代表,无论如何,这很可能是无用的,因为这个数字最有可能被支持"通过double,它将与在内置浏览器的C或C ++中一致。没有绝对的方法可以从JavaScript执行此操作,因为我们可以访问的是"对象& #34;甚至当我们在控制台中查看Number时,我们正在查看.toString版本。就此而言,我认为这是唯一重要的形式,因为它将从二进制文件中确定,并且如果对于语句n1 === n2 && n1.toString() !== n2.toString()你只能找到一个n1, n2,它只会成为一个失败点。是相关的...

也就是说,我们可以测试字符串版本,实际上它只要测试二进制文件就好,只要我们记住一些奇怪的东西。特别是因为JavaScript引擎/ VM之外没有任何东西触及二进制版本。然而,这会使你受到一种奇怪的具体,可能非常挑剔和摆脱变化的失败点的摆布。仅供参考,以下是reference implementation done completely in JavaScript的摘录,显示转换的复杂性:

    // 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, (e.g. 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.
        // E.g. 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.
                // e.g. 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'.
                // E.g. 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支持,转换只是简单而直接。所以我设计的是这样的:因为我猜想这些实现不同的唯一地方是他们的#34;渲染&#34;字符串。我构建了一个三倍的测试生成器:

  1. 测试&#34;字符串结果&#34;针对参考字符串结果
  2. 测试他们解析的等价物(忽略任何epsilon,我的意思是确切的!)
  3. 测试一个特殊版本的字符串,仅针对四舍五入进行调整&#34;解释&#34;
  4. 为了实现这一点,我们需要访问一个参考版本,我首先想到的是使用一种本地语言,但我发现生成的数字似乎比JavaScript更高,导致更多的错误。那么我想,如果我刚刚在JavaScript引擎中使用了一个实现,那该怎么办呢? WebKit / JavaScriptCore似乎是一个非常好的选择,但是参考构建和运行也需要做很多工作,因此我选择了.NET的简单性,因为它可以访问&#34; jScript&#34;虽然在初步检查时看起来并不理想,但是比原生对手产生更接近的结果。我并不真的想用jScript编写代码,因为语言几乎全部被弃用了,所以我通过CodeDomProvider选择了C#bootstrapping jScript ....经过一点点的修补后,它产生了什么: webkit's JavaScriptCore's Number Prototype (NumberPrototype.cpp) (最后演示酱!!!! 1!),所以现在你可以在所有浏览器中运行它并编译你自己的数据,经过我的个人检查,似乎字符串舍入解释变化在我试过的每个浏览器中,但我还没有找到一个主要的浏览器来处理幕后的数字(其他字符串化)......

    现在为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("e.v");
    
                    this.JsEval = s => (string)evalType.GetMethod("e").Invoke(null, new[] { s });
                }
    
                private readonly Func<string, string> JsEval;
    
            }
        }
    

    或预编译版本,如果您应该选择:http://jsbin.com/afiqil 这是可执行文件,下载风险自负

    screen shot of test generator application

    我应该提一下,该程序的目的只是在文本框中生成一个JavaScript文件并将其复制到剪贴板以方便粘贴,无论您选择哪个,您都可以轻松转换它并将其放在asp上。网络服务器并向结果添加报告以ping服务器并在某个大型数据库中跟踪...如果我需要这些信息,我会对此做些什么..

    ......而且,......我......,...我希望这会帮助你 -

答案 1 :(得分:7)

总结下面的所有内容,除了少数IE的故障外,你可以期待大多数系统的合规性,但应该使用健全性检查作为预防措施(包括命题)。

要验证系统,您可以使用test262中与浮点相关的测试。它们位于http://test262.ecmascript.org/json/ch<2-digit # of spec chapter>.json;测试代码可以用(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()

另一个机会是IEEE 754 compliance tests in C

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

此列表和所有相关测试文件的连接源(截至2012年3月9日,没有来自线束的文件)可在此处找到:http://pastebin.com/U6nX6sKL

答案 2 :(得分:5)

一般的经验法则是,当数字精度很重要并且您只能访问浮点精度数时,所有计算都应该作为整数数学来完成,以最好地确保有效性(确保有效的15位数)数据)。是的,JavaScript中有一堆通用的数字特性,但它们与浮点数内缺乏精确性有关,而与标准的UA实现无关。环顾四下浮点数学的陷阱,它们数不胜数而且非常危险。

我觉得我应该稍微详细说明一下,例如我写了一个程序(用JavaScript),它使用基本的微积分来确定多边形的面积,尺寸以米或英尺为单位。程序不是按原样进行计算,而是将所有内容转换为微米并在那里进行计算,因为一切都会更加完整。

希望这有助于确认


回应您的澄清,评论和疑虑

我不会在下面完整地重复我的评论,但简短的回答是没有人能够说100%的设备EVERY IMPLEMENTATION是100%。期。我可以说和其他人会告诉你一样,那是在 当前的主流浏览器 我还没有看到或听说任何涉及浮点数的浏览器特定的有害bug。但是你的问题本身就是一把双刃剑,因为你想“依赖”“不可靠”的结果,或者只是你希望所有的浏览器“始终不一致” - 换句话说,不是试图确保狮子会玩取物,你的时间会更好地寻找一只狗,这意味着:你可以依靠110%的整数数学和所说的数学结果,对于已经向您建议的字符串数学也是如此......

祝你好运 -

答案 3 :(得分:4)

“我对分布式Web应用程序有一个特定的用途,只有在我可以依赖所有浏览器的一致结果时才能使用。”

然后答案是否定的。您不能在规范上中继告诉您浏览器正确处理浮动。 Chrome每6周更新一次,因此即使您有规格,Chrome也可能会在下一版本中更改此行为。

在计算运行之前,您必须在每次测试您的假设之​​前继续进行功能测试。

答案 4 :(得分:4)

(编辑:下面提到的错误已于2016年3月3日修复。所以我的回答现在是“可能”。)

不幸的是答案是否定的。 v8中至少有一个突出的错误,由于双舍入,意味着它可能与32位Linux上的IEEE 754双精度不匹配。

可以通过以下方式测试:

9007199254740994 + 0.99999 === 9007199254740994

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

答案 5 :(得分:3)

也许您应该使用库进行计算。例如,bignumber可以很好地处理浮点数。在这里,您应该从环境变化中进行保存,因为它使用了自己的存储格式。

答案 6 :(得分:0)

这是计算机时代的问题。如果你问那些使用汇编语言成熟的老程序员,他们会告诉你,你以不同的格式存储重要的数字,并以类似的方式对它们进行操作。

例如,货币值可以通过将浮点值乘以100来保存为整数(以保持2位小数不变)。然后,您可以安全地进行计算,当您必须显示最终结果时,将其除以100.根据您必须保持安全和安全的小数位数,您可能必须选择100以外的其他数字。很长的价值,对这些问题一直保持谨慎。

到目前为止,这使我在各个平台上取得了令人满意的结果。我只是以这种方式远离浮点运算的细微差别