Кросс-браузер Javascript номер точность - PullRequest
37 голосов
/ 22 февраля 2012

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

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

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

Уточнение -

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

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

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

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

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

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

Ответы [ 7 ]

19 голосов
/ 07 марта 2012

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

Во-первых:

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

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

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

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

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

Все это говорит, давайте перейдем ко второму запросу:

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

Поскольку JavaScript является интерпретируемой платформой, теперь вы должны увидеть, что нет способа протестировать набор script + compiler (VM / engine) + compiler, который скомпилировал компилятор + машина в абсолютной и надежной форме.путь с точки зрения JavaScript.Поэтому, если вы не хотите создать набор тестов, который действует как хост браузера и фактически «заглядывает» в личную память процесса, чтобы обеспечить правильное представление, которое в любом случае было бы бесполезным, поскольку число, скорее всего, «поддерживается»double, и это будет соответствовать, как это происходит в C или C ++, в который встроен браузер. Нет абсолютного способа сделать это из JavaScript, поскольку все, к чему мы имеем доступ, это «объект» и даже когда мы видимNumber в консоли мы смотрим на .toString версию.В связи с этим я бы сказал, что это единственная форма, которая имеет значение, поскольку она будет определена из двоичного кода и станет точкой отказа, если для утверждения: n1 === n2 && n1.toString() !== n2.toString() вы можете найти n1, n2, который имеет отношение к делу ...

Тем не менее, мы можем протестировать строковую версию, и на самом деле это так же хорошо, как тестировать бинарный файл, если иметь в виду несколько странностей.Тем более что ничто вне движка JavaScript / VM никогда не касается бинарной версии.Однако это ставит вас в зависимость от странно специфического, возможно, очень привередливого и готового изменить точку отказа.Просто для справки, вот отрывок из Прототипа номера JavaScriptCore webkit (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. проверяет их проанализированные эквиваленты (игнорируя любые эпсилоны, я имею в виду точные!)
  3. тестирует специальную версию строк, которая корректируется исключительно для "интерпретации" округления

Чтобы выполнить это, нам нужен доступ к эталонной сборке, моей первой мыслью было использовать одну изродной язык, но с этим я обнаружил, что производимые числа, похоже, имеют более высокую точность, чем JavaScript в целом, что приводит к гораздо большему количеству ошибок.И тогда я подумал, а что, если я просто использовал реализацию уже в движке JavaScript.WebKit / JavaScriptCore выглядел действительно хорошим выбором, но было бы много работы, чтобы создать и запустить справочную систему, поэтому я остановился на простоте .NET, так как он имеет доступ к «jScript», хотя и не идеальным, как казалось на начальном этапе.экспертиза дает более близкие результаты, чем родная коллега.Я действительно не хотел кодировать в jScript, так как язык почти устарел, поэтому я выбрал загрузку jScript на C # через CodeDomProvider .... После небольшого поворота вот что получилось: http://jsbin.com/afiqil (наконец, демонстрационный соус !!!! 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://uploading.com/files/ad4a85md/DoubleFloatJs.exe/ это исполняемый файл, скачайте на свой страх и риск

screen shot of test generator application

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

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

7 голосов
/ 09 марта 2012

Подводя итог всему ниже, вы можете ожидать соответствия на большинстве систем, за исключением нескольких глюков 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 тесты на соответствие в 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"
}

этот список и объединенный источник всех соответствующих тестовых файлов (по состоянию на 09.03.2012, файлы из жгута отсутствуют) можно найти здесь: http://pastebin.com/U6nX6sKL

5 голосов
/ 22 февраля 2012

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

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

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


В ответ на ваши разъяснения, комментарии и замечания

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

удачи -ck

4 голосов
/ 14 мая 2013

(РЕДАКТИРОВАТЬ: ошибка, упомянутая ниже, была закрыта как исправлено 3 марта 2016 года. Поэтому мой ответ теперь "возможно".)

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

Это можетпроверить:

9007199254740994 + 0.99999 === 9007199254740994

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

4 голосов
/ 06 марта 2012

«Я имею в виду конкретное использование распределенного веб-приложения, которое будет работать только в том случае, если я смогу полагаться на согласованные результаты во всех браузерах».

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

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

3 голосов
/ 06 марта 2012

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

0 голосов
/ 13 марта 2012

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

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

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

...