Подтвердить что ты не робот

Кросс-браузер Javascript число точность

В JavaScript, числа определяются как 64-битная двойная точность. У меня есть особое применение для распределенного веб-приложения, которое будет работать только в том случае, если я могу полагаться на последовательные результаты во всех браузерах.

Несмотря на спецификацию, использующую стандарт IEEE, я, естественно, подозреваю, что могут быть небольшие различия в реализациях библиотеки математики или даже базового оборудования, что может вызвать сложные ошибки.

Есть ли какой-либо источник данных совместимости или надежный набор тестов для проверки вычислений с двойной точностью в браузере? В частности, мне также необходимо рассмотреть мобильные браузеры (обычно на основе ARM).

Уточнение -

Это вопрос совместимости браузеров. Я пытаюсь понять, можно ли полагаться на все браузеры для обработки чисел надежным, последовательным и повторяемым способом, как определено для плавающей точки IEEE. В большинстве языков это безопасное предположение, но интересно, что в браузере есть небольшая неопределенность.

Были некоторые отличные советы о том, как избежать проблем с плавающей запятой из-за отсутствия точности и ошибок округления. В большинстве случаев, если вам нужна точность, вы должны следовать этому совету!

По этому вопросу я не пытаюсь избежать проблемы, но понимаю ее. Числа с плавающей запятой по своей сути неточны по дизайну, но до тех пор, пока будет предпринята определенная забота о том, как создаются сборки, неточность может быть полностью предсказуемой и последовательной. IEEE-754 описывает это до уровня детализации, который может иметь только орган стандартов.

Я решил предложить небольшую награду, если кто-нибудь может привести,

  • Подлинные данные совместимости, связанные с реализацией номеров IEEE в основных браузерах.
  • Набор тестов, предназначенный для проверки реализации внутри браузеров, включая проверку правильного внутреннего использования 64-битного числа с плавающей запятой (53 бит мантиссы).

В этом вопросе я не ищу альтернативные варианты, обходные пути или способы избежать проблемы. Благодарим вас за предложения.

4b9b3361

Ответ 1

(EDIT: ошибка, упомянутая ниже, была закрыта как 3 марта 2016 года. Поэтому мой ответ теперь "возможно".)

К сожалению, ответ отрицательный. В v8 есть по крайней мере одна выдающаяся ошибка, которая из-за двойного округления означает, что она может не соответствовать двойной точности IEEE 754 на 32-разрядной Linux.

Это можно протестировать с помощью:

9007199254740994 + 0.99999 === 9007199254740994

Я могу проверить, что это не удается (левая сторона 9007199254740996) в Chrome 26.0.1410.63 работает на 32-разрядном Ubuntu. Он проходит на Firefox 20.0 в той же системе. По крайней мере, этот тест должен быть добавлен в ваш тестовый набор и, возможно, test262.

Ответ 2

Это просто для удовольствия, как вы уже заявили, и я создал новый ответ, потому что этот находится в другом ключе. Но я все еще чувствую, что есть несколько случайных прохожих, которые игнорируют бесполезность проблемы. Поэтому давайте начнем с адресации ваших точек:

Во-первых:

Подлинные данные совместимости, относящиеся к реализации IEEE номера в основных браузерах.

не существует, и в этом отношении даже не имеет смысла, IEEE - это всего лишь орган стандартов...? Я не уверен, что это расплывчато по назначению или по несчастному случаю, я предположим, что вы пытались сказать IEEE 754, но там во лжи руб... есть технически 2 версии этого стандарта IEEE 754-2008 И IEEE 754-1985. По сути, прежний новичок и обращается к последним оплошностям. Любой здравомыслящий человек предположил бы, что любая поддерживаемая реализация JavaScript будет обновляться до последнего и самого большого стандарта, но любой здравомыслящий человек должен знать JavaScript лучше, чем это, и даже если JavaScript не был сумасшедшим, нет спецификации, говорящей, что реализация должна быть/оставаться в курсе последних событий (проверить спецификацию ECMA, если вы мне не верите, они даже не говорят "версии" ). Чтобы усугубить вопрос, стандарт IEEE 754-2008 для арифметики с плавающей точкой поддерживает два форматы кодирования: формат десятичной кодировки и формат двоичного кодирования. Что, как и следовало ожидать, совместимы друг с другом в том смысле, что вы можете идти туда и обратно без потери данных, но предполагая, что у нас есть доступ к двоичному представлению числа, чего у нас нет (без привязки отладчика и глядя на магазин по старому школьному пути)

Однако, из того, что я могу сказать, кажется, что общепринятой практикой является "вернуть" JavaScript Number со старомодным double, что, конечно же, означает, что мы находимся во власти компилятора, используемого для создания браузер. Но даже в этой сфере мы не можем и не должны принимать равенство, даже если все компиляторы были на одной и той же версии стандарта (их нет), и даже если все компиляторы реализовали стандарт целиком (они нет). Вот выдержка из этой статьи, которую я счел интересным, стоящим и актуальным для этого диалогового окна читать...

Многие программисты любят полагать, что они могут понять поведение программы и доказать, что она будет работать правильно без ссылки компилятору, который компилирует его или компьютер, который его запускает. Во многих способы, поддерживающие это убеждение, являются стоящей целью для разработчиков компьютерные системы и языки программирования. К сожалению, когда приходит к арифметике с плавающей точкой, цель практически невозможна достигать. Авторы стандартов IEEE знали это, и они не пытались добиться этого. В результате, несмотря на почти универсальные соответствие (большинство) стандарта IEEE 754 на всем компьютере промышленности, программисты портативного программного обеспечения должны продолжать справляться с непредсказуемая арифметика с плавающей запятой.

Обнаружив, что я нашел эту эталонную реализацию, полностью выполненную в JavaScript (примечание: я на самом деле не проверял достоверность реализации),

Все сказанное, перейдем к вашему второму запросу:

Набор тестов, предназначенный для проверки реализации в рамках браузеров, включая проверку правильного внутреннего использования 64-битного число с плавающей запятой (53 бит мантиссы).

Поскольку JavaScript является интерпретируемой платформой, теперь вы должны увидеть, что нет возможности проверить набор компилятора script + компилятор (VM/engine) +, который скомпилировал компилятор + машину в абсолютном и надежном виде из точки JavaScript. Поэтому, если вы не хотите создавать тестовый пакет, который действует как хост-браузер и фактически "заглядывает" в частную память процесса, чтобы обеспечить достоверное представление, которое было бы бесполезным, скорее всего, так или иначе, поскольку число, скорее всего, "поддерживается" a double, и это будет соответствовать тому, что и в 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 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, и преобразование ничего, кроме простого и простого. Итак, я разработал это: поскольку я предполагаю, что единственное место, где эти реализации будут отличаться, - это их "рендеринг" строк. Я построил тестовый генератор в три раза:

  • проверяет "результат строки" на результат ссылочной строки
  • проверяет их проанализированные эквиваленты (игнорируя любой эпсилон, я имею в виду точный!)
  • проверяет специальную версию строк, которая только настраивает для округления "интерпретацию"

Чтобы достичь этого, нам нужен доступ к эталонной сборке, моя первая мысль заключалась в том, чтобы использовать ее на родном языке, но с этим я обнаружил, что полученные номера, как представляется, имеют более высокую точность, чем JavaScript, что в целом приводит к гораздо большему количеству ошибок. Итак, тогда я подумал, что, если я просто использовал реализацию уже внутри движка JavaScript. WebKit/JavaScriptCore показался действительно хорошим выбором, но также потребовалось бы много работы по созданию и запуску эталонной сборки, поэтому я выбрал простоту .NET, так как у нее есть доступ к "jScript", хотя не идеальный, казалось, при первоначальном чтобы получить более близкие результаты, чем родной аналог. Я не очень хотел кодировать в jScript, так как язык почти устарел, поэтому я выбрал С# для начальной загрузки jScript через CodeDomProvider.... После небольшого переделания здесь, что он произвел: http://jsbin.com/afiqil (наконец, demo sauce!!!! 1!), так что теперь вы можете запустить его во всех браузерах и скомпилировать свои собственные данные, которые после мой личный осмотр кажется, что строка округлой интерпретации меняется в КАЖДОМ браузере, который я пробовал, однако мне еще предстоит найти крупный браузер, который обрабатывал номера за кулисами (другие, которые строят) по-разному...

теперь для соуса С#:

    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/ это исполняемый файл, загрузка на свой страх и риск

screen shot of test generator application

Я должен упомянуть, что цель программы - просто создать файл JavaScript в текстовом поле и скопировать его в буфер обмена для удобства вставки в любое место, где бы вы ни выбрали, вы могли бы легко повернуть это и поместить его на asp.NET server и добавлять отчеты к результатам для ping сервера и отслеживать в какой-то массивной базе данных... вот что я сделал бы с этим, если бы я хотел получить информацию.

... и,... я,... провел, надеюсь, это поможет вам -ck

Ответ 3

Подводя итог всему ниже, вы можете ожидать соответствия большинства систем, за исключением нескольких ошибок IE, но должны использовать проверку работоспособности в качестве меры предосторожности (предложение включено).

Чтобы проверить систему, вы можете использовать тесты, связанные с float, из 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 в C.

Соответствующие разделы из теста262 (те, которые сравнивают числа с плавающей запятой):

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

этот список и конкатенированный источник всех соответствующих тестовых файлов (по состоянию на 3/9/2012, без файлов из жгута проводов) можно найти здесь: <а10 >

Ответ 4

Общее правило заключается в том, что, когда точность чисел важна и у вас есть только доступ к номерам чисел с плавающей запятой, все ваши вычисления должны выполняться как целочисленная математика, чтобы наилучшим образом гарантировать достоверность (где вы уверены, что 15 цифр, безусловно, действительны данные). И да, есть куча общих числовых особенностей в JavaScript, но они больше связаны с отсутствием точности в числах с плавающей запятой, а не с реализацией UA стандарта. Оглянитесь вокруг ловушек математики с плавающей запятой, они многочисленны и предательские.

Я чувствую, что мне нужно немного разобраться, например, я написал программу (в JavaScript), которая использовала базовое исчисление для определения области многоугольника с размерами, указанными в метрах или футах. Вместо того, чтобы делать вычисления как есть, программа конвертировала все в микрометры и делала там свои вычисления, поскольку все было бы более интегральным.

надеюсь, что это поможет -ck


В ответ на ваши пояснения, замечания и замечания

Я не буду повторять свои комментарии ниже в целом, однако короткий ответ - никто никогда не сможет сказать, что КАЖДЫЙ ОСУЩЕСТВЛЕНИЕ составляет 100% на 100% устройств. Период. Я могу сказать, и другие скажут вам то же самое, что в текущих основных браузерах я не видел и не слышал о какой-либо конкретной вредоносной ошибке браузера, содержащей числа с плавающей запятой. Но ваш вопрос является своего рода обоюдоострым мечом, поскольку вы хотите "полагаться" на "ненадежные" результаты или просто хотите, чтобы все браузеры были "последовательно несогласованными" - другими словами, вместо того, чтобы пытаться убедиться, что лев будет играть в избранное, ваше время будет лучше потрачено на поиски собаки, а это означает: вы можете полагаться на 110% на целочисленную математику И результаты указанной математики, то же самое идет для строковой математики, которая уже была предложена вам...

удачи -ck

Ответ 5

"У меня есть особое применение для распределенного веб-приложения, которое будет работать, только если я могу полагаться на согласованные результаты во всех браузерах".

Тогда ответ отрицательный. Вы не можете передавать по спецификации, чтобы сообщить вам, что браузер правильно обрабатывает поплавки. Обновление Chrome каждые 6 недель, поэтому, даже если у вас есть спецификации, Chrome может изменить поведение в следующей версии.

Вам нужно ретранслировать на тестирование функций, которые проверяют ваши предположения перед каждым запуском ваших вычислений.

Ответ 6

Может быть, вы должны использовать библиотеку для своих вычислений. Например bignumber имеет хорошую обработку чисел с плавающей запятой. Здесь вы должны сохранить изменения в среде, поскольку он использует собственный формат хранения.

Ответ 7

Это проблема с возрастом в вычислительной технике. И если вы спросите старых программистов, которые созрели на ассемблере, они скажут вам, что вы храните важные номера в другом формате и делаете манипуляции с ними аналогичным образом.

Например, значение валюты можно сохранить как целое, умножив значение float на 100 (чтобы сохранить 2 десятичных знака неповрежденными). Затем вы можете безопасно выполнять вычисления и когда вам нужно отобразить окончательный результат, разделите его на 100. В зависимости от того, сколько десятичных разрядов вы должны сохранять в безопасности и безопаснее, вам может потребоваться выбрать другое число, отличное от 100. Храните вещи в долгое значение и заботиться о таких проблемах когда-либо.

Вот что дает мне удовлетворительные результаты на всех платформах. Я просто держу себя подальше от арифметических нюансов с плавающей запятой.