Кросс-браузер Javascript число точность
В JavaScript, числа определяются как 64-битная двойная точность. У меня есть особое применение для распределенного веб-приложения, которое будет работать только в том случае, если я могу полагаться на последовательные результаты во всех браузерах.
Несмотря на спецификацию, использующую стандарт IEEE, я, естественно, подозреваю, что могут быть небольшие различия в реализациях библиотеки математики или даже базового оборудования, что может вызвать сложные ошибки.
Есть ли какой-либо источник данных совместимости или надежный набор тестов для проверки вычислений с двойной точностью в браузере? В частности, мне также необходимо рассмотреть мобильные браузеры (обычно на основе ARM).
Уточнение -
Это вопрос совместимости браузеров. Я пытаюсь понять, можно ли полагаться на все браузеры для обработки чисел надежным, последовательным и повторяемым способом, как определено для плавающей точки IEEE. В большинстве языков это безопасное предположение, но интересно, что в браузере есть небольшая неопределенность.
Были некоторые отличные советы о том, как избежать проблем с плавающей запятой из-за отсутствия точности и ошибок округления. В большинстве случаев, если вам нужна точность, вы должны следовать этому совету!
По этому вопросу я не пытаюсь избежать проблемы, но понимаю ее. Числа с плавающей запятой по своей сути неточны по дизайну, но до тех пор, пока будет предпринята определенная забота о том, как создаются сборки, неточность может быть полностью предсказуемой и последовательной. IEEE-754 описывает это до уровня детализации, который может иметь только орган стандартов.
Я решил предложить небольшую награду, если кто-нибудь может привести,
- Подлинные данные совместимости, связанные с реализацией номеров IEEE в основных браузерах.
- Набор тестов, предназначенный для проверки реализации внутри браузеров, включая проверку правильного внутреннего использования 64-битного числа с плавающей запятой (53 бит мантиссы).
В этом вопросе я не ищу альтернативные варианты, обходные пути или способы избежать проблемы. Благодарим вас за предложения.
Ответы
Ответ 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, но должны использовать проверку работоспособности в качестве меры предосторожности (предложение включено).
-
Спецификация ECMAScript (оба варианта 3 и 5) очень точна по особенностям IEEE 754, таким как конверсии, режим округления, переполнение /underflow и даже подписанные нули (разделы 5.2, 8.5, 9. *, 11.5. *, 11.8.5, 15.7 и 15.8 также относятся к поплавкам). Это не просто "оставляет вещи основной реализации". Между vv нет явных различий. 3 и 5 и все основные браузеры поддерживают v.3 как минимум. Поэтому его правила соблюдаются всеми, по крайней мере номинально. Посмотрим...
-
Ни один браузер не пропускает test262 тесты соответствия ECMAScript полностью (WP #Conformance tests). Тем не менее, никакие ошибки test262 найденные в Google, не связаны с плавающей точкой.
-
IE5.5 + ([MS-ES3]) сообщает о расхождениях в Number.toFixed
, Number.toExponential
, Number.toPrecision
, Другие отличия не связаны с плавающей точкой. Я не смог запустить test262 в IE8;
- FF использует специальные типы для числа и обязательного преобразования fns между ними и типами C (см., например, руководство пользователя JSAPI), в котором говорится, что вещи находятся в факт, не переданный C. Тест на FF10 не показал никаких ошибок, связанных с поплавком;
- Пользователи Opera сообщали о проблемах, связанных с float, полгода назад;
- Неисправности Webkit test262 не связаны с плавающей точкой.
Чтобы проверить систему, вы можете использовать тесты, связанные с 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. Храните вещи в долгое значение и заботиться о таких проблемах когда-либо.
Вот что дает мне удовлетворительные результаты на всех платформах. Я просто держу себя подальше от арифметических нюансов с плавающей запятой.