跨浏览器Javascript数字精度

发布于 2025-01-08 02:02:30 字数 699 浏览 0 评论 0原文

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

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

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

澄清 -

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

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

对于这个问题,我并不是要回避问题,而是要理解它。浮点数在设计上本质上是不准确的,但只要注意构建的方式,不准确度是完全可以预测和一致的。 IEEE-754 对此的描述达到了只有标准机构才能做到的详细程度。

我决定提供一笔小额赏金

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

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

Within JavaScript, numbers are defined as 64bit double-precision. 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.

Despite the spec using the IEEE standard, I naturally have a suspicion that there may be tiny differences in implementations of the maths library or even the underlying hardware, which could cause compound errors.

Is there any source of compatibility data, or a reliable test suite to verify double precision calculations in the browser? In particular, I also need to consider mobile browsers (usually ARM based).

Clarification -

This is a question about browser compatibility. I'm trying to understand whether all browsers can be relied upon to treat numbers in a reliable, consistent and repeatable way as defined for IEEE floating point. In most languages this is a safe assumption, but it's interesting that there's a little uncertainty about this in the browser.

There's been some great advice on how to avoid floating point problems due to lack of precision and rounding errors. In most cases, if you require accuracy you should follow this advice!

For this question, I'm not trying to avoid the problem but understand it. Floating point numbers are inherently inaccurate by design, but as long as some care is taken with how builds are made that inaccuracy can be completely predictable and consistent. IEEE-754 describes this to a level of detail that only a standards body could.

I've decided to offer a small bounty if anyone can cite,

  • Genuine compatibility data relating to the implementation of IEEE numbers in mainstream browsers.
  • A test suite intended to verify the implementation within the browsers, including verifying the correct internal use of a 64 bit floating point number (53 bit mantissa).

In this question I'm not looking for alternative options, workarounds or ways to avoid the problem. Thank you for the suggestions.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

桃扇骨 2025-01-15 02:02:31

这只是为了好玩,正如您已经说过的,我创建了一个新答案,因为这个答案是不同的。但我仍然觉得有一些路人忽视了这个问题的无用性。因此,让我们首先阐述您的观点:

首先:

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

不存在,就此而言甚至没有任何意义, IEEE 只是一个标准机构...?我不确定这种模糊是有意还是无意,我假设您试图说 IEEE 754,但问题就在这里......技术上该标准有 2 个版本 IEEE 754-2008IEEE 754-1985。基本上前者是较新的并且解决了后者的疏忽。任何理智的人都会认为任何维护的 JavaScript 实现都会更新到最新和最好的标准,但任何理智的人都应该比这更好地了解 JavaScript,即使 JavaScript 并不疯狂,也没有规范说该实现必须是/保持最新(如果您不相信我,他们甚至不谈论“版本”)。更复杂的是,IEEE 浮点运算标准 754-2008 支持两种
编码格式:十进制编码格式、二进制编码格式。正如预期的那样,它们是相互兼容的,因为您可以来回移动而不会丢失数据,但这是假设我们可以访问数字的二进制表示形式,但我们不能 (无需附加调试器并以老式方式查看存储)

但是,据我所知,通常的做法是用老式的 “返回”JavaScript Number double 这当然意味着我们处于取决于用于实际构建浏览器的编译器。但即使在这个领域,我们也不能也不应该假设平等,即使所有编译器都使用同一版本的标准(它们不是),即使所有编译器完整地实现了该标准(它们不)。以下是这篇论文的摘录,我认为这篇论文有趣、有价值且相关-此对话框读...

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

同时发现我还发现了这个完全用 JavaScript 完成的参考实现(注意:我实际上还没有验证实施的有效性)。

说了这么多,让我们继续讨论您的第二个请求:

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

由于 JavaScript 是一个解释平台,您现在应该看到,没有办法以绝对可靠的方式测试脚本 + 编译器(VM/引擎)+ 编译编译器 + 机器的编译器的集合 JavaScript 的重点。因此,除非您想构建一个充当浏览器主机的测试套件,并实际上“窥视”进程的私有内存以确保有效的表示,否则这很可能是徒劳的,因为该数字很可能是由一个 double ,这将符合浏览器内置的 C 或 C++ 中的要求。 JavaScript 中没有绝对的方法可以做到这一点,因为我们只能访问“对象”即使我们查看Number在控制台中,我们正在查看 .toString 版本。就此而言,我认为这是唯一重要的形式,因为它将从二进制文件中确定,并且只有在语句: n1 === n2 && 时才会成为失败点。 n1.toString() !== n2.toString() 你可以找到一个相关的 n1, n2 ...

也就是说,我们可以测试字符串版本,实际上它只要我们记住一些奇怪的地方,就和测试二进制文件一样好。特别是因为 JavaScript 引擎/VM 之外的任何东西都不会触及二进制版本。然而,这会让你受到一个奇怪的、特定的、可能非常挑剔的、随时可能改变的故障点的摆布。仅供参考,这里摘录自 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, (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 支持,并且转换为绝非简单明了。所以我的设计是这样的:因为我推测这些实现的唯一不同之处是它们对字符串的“渲染”。我构建了一个包含三部分的测试生成器:

  1. 根据参考字符串结果测试“字符串结果”
  2. 测试其解析的等效项(忽略任何 epsilon,我的意思是精确!)
  3. 测试字符串的特殊版本,该版本仅针对舍入“解释”进行调整“

为了实现这一目标,我们需要访问参考构建,我的第一个想法是使用本地语言的参考构建,但我发现生成的数字似乎比 JavaScript 具有更高的精度,通常会导致更多错误。然后我想,如果我只使用 JavaScript 引擎中已有的实现会怎样。 WebKit/JavaScriptCore 似乎是一个非常好的选择,但构建和运行参考也需要做很多工作,所以我选择了 .NET 的简单性,因为它可以访问“jScript”,虽然最初看起来并不理想考试产生比本地同行更接近的结果。我真的不想用 jScript 进行编码,因为该语言几乎已被弃用,所以我选择通过 CodeDomProvider 使用 C# 引导 jScript。经过一番修改后,它生成了以下内容:http://jsbin.com/afiqil (最后是演示酱!!!1!),所以现在您可以在所有浏览器中运行它并编译您自己的数据,根据我个人的检查,我尝试过的每个浏览器中的字符串舍入解释似乎有所不同,但是我还没有找到一个主要的浏览器来处理幕后的数字(除了stringify-ing)不同...

现在是 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://uploading.com/files/ad4a85md/DoubleFloatJs.exe/ 这是一个可执行文件,下载风险自负

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

我应该提到该程序的目的只是在文本框中生成一个 JavaScript 文件并将其复制到剪贴板以供使用方便为了粘贴到您选择的任何地方,您可以轻松地将其翻转并将其放在 asp.net 服务器上,并将报告添加到结果中以 ping 服务器并在某些大型数据库中进行跟踪...这就是我会做的,如果我想要信息..

...并且,...我,...花了我希望这对您有帮助 -ck

This is just for fun, as you already stated and I created a new answer because this one is in a different vein. But I still feel like there are a few random passerby's who are ignoring the futility of the problem. So let's start by addressing your points:

Firstly:

Genuine compatibility data relating to the implementation of IEEE
numbers in mainstream browsers.

doesn't exist, and for that matter doesn't even make any sense, IEEE is just a standards body...? I am not sure if this vague on purpose or on accident, I will assume you were trying to say IEEE 754, but there in lies the rub... there are technically 2 versions of this standard IEEE 754-2008 AND IEEE 754-1985. Basically the former is newer and addresses the latter's oversights. Any sane person would assume that any maintained JavaScript implementation would update to the latest and greatest standard, but any sane person should know JavaScript better than that, and even if JavaScript wasn't crazy, there is no specification saying that the implementation has to be/stay up to date (check the ECMA spec yourself if you don't believe me, they don't even talk "versions"). To compound the matters further the IEEE Standard 754-2008 for Floating-Point Arithmetic supports two
encoding formats: the decimal encoding format, and the binary encoding format. Which as would be expected are compatible with each other in the sense that you can go back and forth without loss of data, but that's assuming we have access to the binary representation of the number, which we don't (without attaching a debugger and looking at the store the old school way)

However, from what I can tell it seems it is general practice to "back" a JavaScript Number with an old fashioned double which of course means that we are at the mercy of the compiler used to actually build the browser. But even in that realm, we can't and shouldn't be assuming equality even if all the compilers were on the same version of the standard (they aren't) and even if all the compilers implemented the standard in its entirety (they don't). Here's an excerpt from this paper, which I have deemed an interesting, worthwhile and relevant-to-this-dialog read...

Many programmers like to believe that they can understand the behavior
of a program and prove that it will work correctly without reference
to the compiler that compiles it or the computer that runs it. In many
ways, supporting this belief is a worthwhile goal for the designers of
computer systems and programming languages. Unfortunately, when it
comes to floating-point arithmetic, the goal is virtually impossible
to achieve. The authors of the IEEE standards knew that, and they
didn't attempt to achieve it. As a result, despite nearly universal
conformance to (most of) the IEEE 754 standard throughout the computer
industry, programmers of portable software must continue to cope with
unpredictable floating-point arithmetic.

While finding that I also found this reference implementation done completely in JavaScript (note: I haven't actually verified the validity of the implementation).

All that said, let's move on to your second request:

A test suite intended to verify the implementation within the
browsers, including verifying the correct internal use of a 64 bit
floating point number (53 bit mantissa).

Since JavaScript is an interpreted platform you should see now that there is no way to test the set of script + compiler (VM/engine) + compiler that compiled the compiler + machine in an absolute and reliable way from the point of JavaScript. So unless you want to build a test suite that acts as a browser host and actually "peeks" into the private memory of the process to ensure a valid representation, which would be fruitless most likely anyway since the number are most likely "backed" by a double and that is going to conform as it does in the C or C++ that browser was built in. There is no absolute way to do this from JavaScript since all we have access to is the "object" and even when we view the Number in a console we are looking at a .toString version. For that matter I would posit that this is the only form that matters since it will be determined from the binary and would only become a point of failure if for the statement: n1 === n2 && n1.toString() !== n2.toString() you could find an n1, n2 that is relevant...

That said, we can test the string version and in reality it is just as good as testing the binary as long as we keep a few oddities in mind. Especially since nothing outside the JavaScript engine/VM ever touches the binary version. However this puts you at the mercy of an oddly specific, possibly very finicky and poised to be changed point of failure. Just for reference, here is an excerpt from webkit's JavaScriptCore's Number Prototype (NumberPrototype.cpp) displaying the complexity of the conversion:

    // 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;
    }

... as you can see, the number here is backed by a traditional double and the conversion is anything but simple and straightforward. So what I devised was this: since I conjecture that the only spot that these implementations will differ are their "rendering" to strings. I built a test generator that is three fold:

  1. tests the "string result" against a reference string result
  2. tests their parsed equivalents (ignoring any epsilon, I mean exact!)
  3. tests a special version of the strings that solely adjusts for the rounding "interpretation"

To accomplish this we need access to a reference build, my first thought was to use one from a native language but with that I found that the numbers produced seemed to have a higher precision than JavaScript in general leading to far more errors. So then I thought, what if I just used an implementation already inside a JavaScript engine. WebKit/JavaScriptCore seemed like a really good choice but it would have also been a lot of work to get the reference build up and running so I opted for the simplicity of .NET since it has access to "jScript" while not ideal seemed upon initial examination to produce closer results than the native counterpart. I didn't really want to code in jScript since the language is all but deprecated so I opted for C# bootstrapping jScript through a CodeDomProvider.... After a little tinkering here's what it produced: http://jsbin.com/afiqil (finally demo sauce!!!!1!), so now you can run it in all browsers and compile your own data, which upon my personal inspection it seems string rounding interpretation varies in EVERY browser I tried, however I've yet to find a major browser that handled the numbers behind the scenes (other that the stringify-ing) differently...

now for the C# sauce:

    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;

        }
    }

or a pre-compiled version if you should choose: http://uploading.com/files/ad4a85md/DoubleFloatJs.exe/ this is an executable, download at your own risk

screen shot of test generator application

I should mention that the purpose of the program is just to produce a JavaScript file in a text box and copy it to the clipboard for convenience for pasting wherever you choose, you could easily turn this around and put it on an asp.net server and add reporting to results to ping the server and keep track in some massive database... which is what I would do to it if I desired the information..

...and, ...I'm, ...spent I hope this helps you -ck

梓梦 2025-01-15 02:02:31

总结下面的所有内容,除了一些 IE 的故障之外,您可以期望大多数系统都符合要求,但应该使用健全性检查作为预防措施(包括建议)。

要验证系统,您可以使用 test262 中与浮动相关的测试。它们位于 http://test262.ecmascript.org/json/ch<规范章节的 2 位数字>.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()

另一个机会是 C 语言的 IEEE 754 合规性测试

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

Summarizing everything below, you can expect compliance on the majority of systems save for a few IE's glitches, but ought to use a sanity check as a precaution (proposition is included).

To validate a system, you can use float-related tests from test262. They are located at http://test262.ecmascript.org/json/ch<2-digit # of spec chapter>.json; test code can be extracted with (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()

Another opportunity is IEEE 754 compliance tests in C.

Relevant sections from test262 (ones that compare floating point numbers) are as follows:

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

this list and the concatenated source of all the relevant test files (as of 3/9/2012, no files from the harness) can be found here: http://pastebin.com/U6nX6sKL

三生池水覆流年 2025-01-15 02:02:31

一般经验法则是,当数字精度很重要并且您只能访问浮点精度数字时,所有计算都应以整数数学形式完成,以最好地确保有效性(确保 15 位数据确实有效)。是的,JavaScript 中有很多通用的数字特性,但它们更多地与浮点数缺乏精度相关,而不是与标准的 UA 实现相关。环顾四周,寻找浮点数学的陷阱,它们数量众多且危险。

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

希望这会有所帮助 -ck


为了回应您的澄清、评论和担忧

我不会在下面完整地重复我的评论,但简短的回答是没有人会这样做永远可以说每个实现都是100% 100% 的设备。时期。我可以说,其他人也会告诉你同样的事情,那就是在当前的主要浏览器上,我没有看到也没有听说过任何涉及浮点数的浏览器特定的有害错误。但你的问题本身就是一把双刃剑,因为你想“依赖”“不可靠”的结果,或者只是你希望所有浏览器“始终不一致”强> - 换句话说,你的时间最好花在寻找一只狗上,而不是试图确保狮子会玩取球游戏,这意味着:你可以110%依赖整数数学以及所述数学的结果,同样如此对于已经向您建议的字符串数学...

祝你好运 -ck

General rule of thumb is that when number precision is important and you only have access to floating point precision numbers, all of your calculations should be done as integer math to best ensure validity (where you're assured 15 digits of assuredly valid data). And yes there are a bunch of general numeric idiosyncrasies in JavaScript but they are more associated with the lack of precision within floating point numbers and not with UA implementations of the standard. Look around for the pitfalls of floating point math, they're numerous and treacherous.

I feel as I should elaborate a little, for instance I wrote a program (in JavaScript) that used basic calculus to determine the area of polygon with dimensions given in meters or feet. Instead of doing the calculations as is, the program converted everything to micrometers and did its calculations there as everything would be more integral.

hope this helps -ck


In response to your clarification, comments and concerns

I'm not going to repeat my comments below in their entirety, however the short answer is no one will ever be able to say that EVERY IMPLEMENTATION is 100% on 100% of devices. Period. I can say and others will tell you the same, is that on the current major browsers I have not seen nor heard of any browser specific detrimental bug involving floating point numbers. But your question itself is kind of a double edged sword since you want to "rely" upon "unreliable" results, or simply that you want all the browsers to be "consistently inconsistent" - in other words instead of trying make sure a lion will play fetch, your time would be better spent looking for a dog, meaning: you can rely 110% on integer math AND the results of said math, the same goes for string math which has already been suggested to you...

good luck -ck

躲猫猫 2025-01-15 02:02:31

(编辑:下面提到的错误已于 2016 年 3 月 3 日修复并关闭。所以我的答案现在是“也许”。)

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

这可以通过以下方式进行测试:

9007199254740994 + 0.99999 === 9007199254740994

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

(EDIT: The bug mentioned below was closed as fixed on 3 Mar 2016. So my answer is now "maybe".)

Unfortunately the answer is no. There is at least one outstanding bug in v8 that, due to double-rounding, means it might not match IEEE 754 double precision on 32-bit Linux.

This can be tested with:

9007199254740994 + 0.99999 === 9007199254740994

I can verify that this fails (the left-hand side is 9007199254740996) on Chrome 26.0.1410.63 running on 32-bit Ubuntu. It passes on Firefox 20.0 on the same system. At the very least, this test should be added to your test suite, and maybe test262.

番薯 2025-01-15 02:02:31

“我对分布式 Web 应用程序有一个特定的用途,只有当我可以在所有浏览器上获得一致的结果时,它才有效。”

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

您必须在每次运行计算之前依赖功能测试来测试您的假设。

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

酒儿 2025-01-15 02:02:31

也许您应该使用库进行计算。例如 bignumber 可以很好地处理浮点数。在这里,您应该免受环境更改的影响,因为它使用自己的存储格式。

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.

无所的.畏惧 2025-01-15 02:02:31

这是计算领域自古以来的一个问题。如果你问从汇编语言成熟的老程序员,他们会告诉你,你以不同的格式存储重要的数字,并以类似的方式对它们进行操作。

例如,可以通过将浮点值乘以 100(以保持 2 位小数不变)将货币值保存为整数。然后,您可以安全地进行计算,当您必须显示最终结果时,将其除以 100。根据您必须保持安全的小数位数,您可能必须选择 100 以外的其他数字。将内容存储在长期价值,永远不用担心此类问题。

这就是迄今为止在各个平台上给我带来满意结果的原因。我只是这样让自己远离浮点算术的细微差别

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文