C'est juste pour le plaisir, comme vous l'avez déjà dit et j'ai créé une nouvelle réponse, car celui-ci est dans une autre veine. Mais je me sens encore comme il y a un peu de hasard passants qui sont ignorant la futilité du problème. Donc, nous allons commencer par aborder vos points:
Tout d'abord:
Véritable compatibilité des données relatives à la mise en œuvre de la norme IEEE
les nombres dans les navigateurs grand public.
n'existe pas, et d'ailleurs n'a même pas de sens, IEEE est juste un corps de normes...? Je ne suis pas sûr si cette vague sur le but ou l'accident, je vais supposer que vous étiez en train de dire la norme IEEE 754, mais il y a là le hic... il n'y a techniquement 2 versions de cette norme IEEE 754-2008 ET IEEE 754-1985. Fondamentalement, l'ancien est plus récente et les adresses de ce dernier oublis. Toute personne saine d'esprit pourrait supposer que tout maintenu JavaScript mise en œuvre mise à jour la plus récente et la plus standard, mais n'importe quelle personne saine d'esprit devrait connaître JavaScript mieux que ça, et même si le JavaScript n'était pas fou, il n'y a pas de spécification de dire que la mise en œuvre doit être/rester à jour (vérifier l'ECMA spec-vous si vous ne me croyez pas, ils n'ont même pas parler de "versions"). Pour compliquer les choses davantage la Norme IEEE 754-2008 de l'Arithmétique à virgule Flottante prend en charge deux
les formats d'encodage: décimal format de codage, et le codage binaire format. Qui que l'on attend sont compatibles les uns avec les autres dans le sens où vous pouvez aller et venir sans perte de données, mais c'est en supposant que nous avons accès à la représentation binaire du nombre, ce qui n'est pas (sans y attacher un débogueur et à la recherche à la boutique old school)
Cependant, de ce que je peux dire, il semble qu'il est de pratique générale de "retour" JavaScript Number
avec l'ancienne, double
ce qui signifie que nous sommes à la merci du compilateur utilisé pour construire le navigateur. Mais, même dans ce domaine, on ne peut pas et ne doit pas être en supposant l'égalité, même si tous les compilateurs ont été sur la même version de la norme (ils ne le sont pas) et même si tous les compilateurs de mise en œuvre de la norme dans son intégralité (ils ne le font pas). Voici un extrait de ce document, que j'ai jugé intéressant, utile et pertinente à cette boîte de dialogue lire...
De nombreux programmeurs aiment à croire qu'ils peuvent comprendre le comportement
d'un programme et de prouver qu'il peut fonctionner correctement sans référence
pour le compilateur compile ou l'ordinateur qui exécute. Dans de nombreux
des moyens, de soutenir cette croyance est un objectif valable pour les concepteurs de
les systèmes informatiques et les langages de programmation. Malheureusement, quand il
vient de l'arithmétique à virgule flottante, le but est pratiquement impossible
à réaliser. Les auteurs de l'IEEE standards savait qu', et ils
ne pas tenter de l'atteindre. En conséquence, en dépit de près universelle
la conformité de (plus de) la norme IEEE 754 la norme tout au long de l'ordinateur
l'industrie, les programmeurs de logiciel portable doit continuer à faire face à
imprévisible arithmétique à virgule flottante.
Mais j'ai aussi trouvé cette référence mise en œuvre fait entièrement en JavaScript (note: je n'ai même pas vérifié la validité de la mise en œuvre).
Tout cela étant dit, passons à votre deuxième demande:
Une suite de tests destinés à vérifier la mise en œuvre au sein de l'
les navigateurs, y compris la vérification de la bonne utilisation interne de 64 bits
nombre à virgule flottante (53 bits de mantisse).
JavaScript est une interprétation d'une plate-forme, vous devriez voir maintenant qu'il n'y a aucun moyen de tester l'ensemble de script + compilateur (VM/moteur) + compilateur le compilateur + machine dans un absolu et de manière fiable à partir du point de JavaScript. Donc, si vous voulez construire une suite de test qui agit comme un navigateur hôte et fait des "pics" dans l'intimité de la mémoire du processus pour assurer une représentation valide, ce qui serait issue la plus probable de toute façon puisque le nombre le plus probable "soutenu" par un double
et qui est conforme comme il le fait dans le C ou le C++ que le navigateur a été construit en. Il n'existe pas de manière absolue à ce faire à partir de JavaScript car tous, nous avons accès est "l'objet" et même lorsque nous considérons l' Number
dans une console, nous sommes à la recherche à un .toString
version. Pour cette question, je voudrais émettre l'hypothèse que c'est la seule forme qui compte, car il sera déterminé à partir du binaire et ne deviennent un point de défaillance si la déclaration: n1 === n2 && n1.toString() !== n2.toString()
vous pouviez trouver une n1, n2
qui est pertinent...
Cela dit, on peut tester la version de chaîne et, en réalité, il est tout aussi bon que les tests binaires tant que nous continuons à quelques bizarreries dans l'esprit. Surtout depuis rien en dehors du moteur JavaScript/VM jamais touche la version binaire. Toutefois, cela vous met à la merci d'une étrange spécifiques, peut-être très capricieux et prête à être changé de point de défaillance. Juste pour référence, voici un extrait de webkit est JavaScriptCore du Nombre de Prototypes (NumberPrototype.cpp) l'affichage de la complexité de la conversion:
// The largest finite floating point number is 1.mantissa * 2^(0x7fe-0x3ff).
// Since 2^N in binary is a one bit followed by N zero bits. 1 * 2^3ff requires
// at most 1024 characters to the left of a decimal point, in base 2 (1025 if
// we include a minus sign). For the fraction, a value with an exponent of 0
// has up to 52 bits to the right of the decimal point. Each decrement of the
// exponent down to a minimum of -0x3fe adds an additional digit to the length
// of the fraction. As such the maximum fraction size is 1075 (1076 including
// a point). We pick a buffer size such that can simply place the point in the
// center of the buffer, and are guaranteed to have enough space in each direction
// fo any number of digits an IEEE number may require to represent.
typedef char RadixBuffer[2180];
// Mapping from integers 0..35 to digit identifying this value, for radix 2..36.
static const char* const radixDigits = "0123456789abcdefghijklmnopqrstuvwxyz";
static char* toStringWithRadix(RadixBuffer& buffer, double number, unsigned radix)
{
ASSERT(isfinite(number));
ASSERT(radix >= 2 && radix <= 36);
// Position the decimal point at the center of the string, set
// the startOfResultString pointer to point at the decimal point.
char* decimalPoint = buffer + sizeof(buffer) / 2;
char* startOfResultString = decimalPoint;
// Extract the sign.
bool isNegative = number < 0;
if (signbit(number))
number = -number;
double integerPart = floor(number);
// We use this to test for odd values in odd radix bases.
// Where the base is even, (e.g. 10), to determine whether a value is even we need only
// consider the least significant digit. For example, 124 in base 10 is even, because '4'
// is even. if the radix is odd, then the radix raised to an integer power is also odd.
// E.g. in base 5, 124 represents (1 * 125 + 2 * 25 + 4 * 5). Since each digit in the value
// is multiplied by an odd number, the result is even if the sum of all digits is even.
//
// For the integer portion of the result, we only need test whether the integer value is
// even or odd. For each digit of the fraction added, we should invert our idea of whether
// the number is odd if the new digit is odd.
//
// Also initialize digit to this value; for even radix values we only need track whether
// the last individual digit was odd.
bool integerPartIsOdd = integerPart <= static_cast<double>(0x1FFFFFFFFFFFFFull) && static_cast<int64_t>(integerPart) & 1;
ASSERT(integerPartIsOdd == static_cast<bool>(fmod(integerPart, 2)));
bool isOddInOddRadix = integerPartIsOdd;
uint32_t digit = integerPartIsOdd;
// Check if the value has a fractional part to convert.
double fractionPart = number - integerPart;
if (fractionPart) {
// Write the decimal point now.
*decimalPoint = '.';
// Higher precision representation of the fractional part.
Uint16WithFraction fraction(fractionPart);
bool needsRoundingUp = false;
char* endOfResultString = decimalPoint + 1;
// Calculate the delta from the current number to the next & previous possible IEEE numbers.
double nextNumber = nextafter(number, std::numeric_limits<double>::infinity());
double lastNumber = nextafter(number, -std::numeric_limits<double>::infinity());
ASSERT(isfinite(nextNumber) && !signbit(nextNumber));
ASSERT(isfinite(lastNumber) && !signbit(lastNumber));
double deltaNextDouble = nextNumber - number;
double deltaLastDouble = number - lastNumber;
ASSERT(isfinite(deltaNextDouble) && !signbit(deltaNextDouble));
ASSERT(isfinite(deltaLastDouble) && !signbit(deltaLastDouble));
// We track the delta from the current value to the next, to track how many digits of the
// fraction we need to write. For example, if the value we are converting is precisely
// 1.2345, so far we have written the digits "1.23" to a string leaving a remainder of
// 0.45, and we want to determine whether we can round off, or whether we need to keep
// appending digits ('4'). We can stop adding digits provided that then next possible
// lower IEEE value is further from 1.23 than the remainder we'd be rounding off (0.45),
// which is to say, less than 1.2255. Put another way, the delta between the prior
// possible value and this number must be more than 2x the remainder we'd be rounding off
// (or more simply half the delta between numbers must be greater than the remainder).
//
// Similarly we need track the delta to the next possible value, to dertermine whether
// to round up. In almost all cases (other than at exponent boundaries) the deltas to
// prior and subsequent values are identical, so we don't need track then separately.
if (deltaNextDouble != deltaLastDouble) {
// Since the deltas are different track them separately. Pre-multiply by 0.5.
Uint16WithFraction halfDeltaNext(deltaNextDouble, 1);
Uint16WithFraction halfDeltaLast(deltaLastDouble, 1);
while (true) {
// examine the remainder to determine whether we should be considering rounding
// up or down. If remainder is precisely 0.5 rounding is to even.
int dComparePoint5 = fraction.comparePoint5();
if (dComparePoint5 > 0 || (!dComparePoint5 && (radix & 1 ? isOddInOddRadix : digit & 1))) {
// Check for rounding up; are we closer to the value we'd round off to than
// the next IEEE value would be?
if (fraction.sumGreaterThanOne(halfDeltaNext)) {
needsRoundingUp = true;
break;
}
} else {
// Check for rounding down; are we closer to the value we'd round off to than
// the prior IEEE value would be?
if (fraction < halfDeltaLast)
break;
}
ASSERT(endOfResultString < (buffer + sizeof(buffer) - 1));
// Write a digit to the string.
fraction *= radix;
digit = fraction.floorAndSubtract();
*endOfResultString++ = radixDigits[digit];
// Keep track whether the portion written is currently even, if the radix is odd.
if (digit & 1)
isOddInOddRadix = !isOddInOddRadix;
// Shift the fractions by radix.
halfDeltaNext *= radix;
halfDeltaLast *= radix;
}
} else {
// This code is identical to that above, except since deltaNextDouble != deltaLastDouble
// we don't need to track these two values separately.
Uint16WithFraction halfDelta(deltaNextDouble, 1);
while (true) {
int dComparePoint5 = fraction.comparePoint5();
if (dComparePoint5 > 0 || (!dComparePoint5 && (radix & 1 ? isOddInOddRadix : digit & 1))) {
if (fraction.sumGreaterThanOne(halfDelta)) {
needsRoundingUp = true;
break;
}
} else if (fraction < halfDelta)
break;
ASSERT(endOfResultString < (buffer + sizeof(buffer) - 1));
fraction *= radix;
digit = fraction.floorAndSubtract();
if (digit & 1)
isOddInOddRadix = !isOddInOddRadix;
*endOfResultString++ = radixDigits[digit];
halfDelta *= radix;
}
}
// Check if the fraction needs rounding off (flag set in the loop writing digits, above).
if (needsRoundingUp) {
// Whilst the last digit is the maximum in the current radix, remove it.
// e.g. rounding up the last digit in "12.3999" is the same as rounding up the
// last digit in "12.3" - both round up to "12.4".
while (endOfResultString[-1] == radixDigits[radix - 1])
--endOfResultString;
// Radix digits are sequential in ascii/unicode, except for '9' and 'a'.
// E.g. the first 'if' case handles rounding 67.89 to 67.8a in base 16.
// The 'else if' case handles rounding of all other digits.
if (endOfResultString[-1] == '9')
endOfResultString[-1] = 'a';
else if (endOfResultString[-1] != '.')
++endOfResultString[-1];
else {
// One other possibility - there may be no digits to round up in the fraction
// (or all may be been rounded off already), in which case we may need to
// round into the integer portion of the number. Remove the decimal point.
--endOfResultString;
// In order to get here there must have been a non-zero fraction, in which case
// there must be at least one bit of the value's mantissa not in use in the
// integer part of the number. As such, adding to the integer part should not
// be able to lose precision.
ASSERT((integerPart + 1) - integerPart == 1);
++integerPart;
}
} else {
// We only need to check for trailing zeros if the value does not get rounded up.
while (endOfResultString[-1] == '0')
--endOfResultString;
}
*endOfResultString = '\0';
ASSERT(endOfResultString < buffer + sizeof(buffer));
} else
*decimalPoint = '\0';
BigInteger units(integerPart);
// Always loop at least once, to emit at least '0'.
do {
ASSERT(buffer < startOfResultString);
// Read a single digit and write it to the front of the string.
// Divide by radix to remove one digit from the value.
digit = units.divide(radix);
*--startOfResultString = radixDigits[digit];
} while (!!units);
// If the number is negative, prepend '-'.
if (isNegative)
*--startOfResultString = '-';
ASSERT(buffer <= startOfResultString);
return startOfResultString;
}
... comme vous pouvez le voir, le nombre ici est soutenu par une traditionnelle double
et la conversion est tout sauf simple. Donc, ce que j'ai trouvé est celle-ci: puisque je pense que la seule place que ces implémentations diffèrent sont leurs "rendu" à cordes. J'ai construit un générateur de test qui est trois fois:
- les tests de la "chaîne de résultats" à l'encontre d'une chaîne de référence résultat
- les tests de leur analysé équivalents (en ignorant tout epsilon, je veux dire exact!)
- teste une version spéciale de ces chaînes qui ajuste uniquement pour l'arrondissement de "l'interprétation"
Pour accomplir cela, nous avons besoin d'un accès à une référence de construire, ma première pensée a été d'utiliser l'une à partir d'une langue maternelle, mais avec ce que j'ai constaté que le nombre de produits semble avoir une plus grande précision que le JavaScript en général menant à bien plus d'erreurs. Alors j'ai pensé que, si je viens d'utiliser une mise en œuvre déjà à l'intérieur d'un moteur JavaScript. WebKit/JavaScriptCore semblait être un très bon choix, mais il aurait également été beaucoup de travail pour obtenir la référence de l'accumulation et de la course j'ai donc opté pour la simplicité d' .NET car elle a accès à "jScript" tout n'est pas idéal semblait lors de l'examen initial pour produire des résultats plus proches que le natif de contrepartie. Je n'ai pas vraiment envie de le code jScript puisque le langage est tout sauf obsolète j'ai donc opté pour C# amorçage jScript par le biais d'un CodeDomProvider
.... Après un peu de bricolage, voici ce qu'elle produit: http://jsbin.com/afiqil (enfin la démo de la sauce!!!!1!), alors maintenant, vous pouvez l'exécuter dans tous les navigateurs et compiler vos propres données, qui, lors de mon inspection, il semble chaîne d'arrondi interprétation varie dans CHAQUE navigateur, j'ai essayé, mais je n'ai pas encore trouver un grand navigateur, qui traite les nombres en coulisses (autres que les stringify-ing) de la même façon...
maintenant, pour le C# de la sauce:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.CodeDom.Compiler;
using System.Reflection;
namespace DoubleFloatJs
{
public partial class Form1 : Form
{
private static string preamble = @"
var successes = [];
var failures = [];
function fpu_test_add(v1, v2) {
return '' + (v1 + v2);
}
function fpu_test_sub(v1, v2) {
return '' + (v1 - v2);
}
function fpu_test_mul(v1, v2) {
return '' + (v1 * v2);
}
function fpu_test_div(v1, v2) {
return '' + (v1 / v2);
}
function format(name, result1, result2, result3, received, expected) {
return '<span style=""display:inline-block;width:350px;"">' + name + '</span>' +
'<span style=""display:inline-block;width:60px;text-align:center;font-weight:bold; color:' + (result1 ? 'green;"">OK' : 'red;"">NO') + '</span>' +
'<span style=""display:inline-block;width:60px;text-align:center;font-weight:bold; color:' + (result2 ? 'green;"">OK' : 'red;"">NO') + '</span>' +
'<span style=""display:inline-block;width:60px;text-align:center;font-weight:bold; color:' + (result3 ? 'green;"">OK' : 'red;"">NO') + '</span>' +
'<span style=""display:inline-block;width:200px;vertical-align:top;"">' + received + '<br />' + expected + '</span>';
}
function check_ignore_round(received, expected) {
return received.length > 8 &&
received.length == expected.length &&
received.substr(0, received.length - 1) === expected.substr(0, expected.length - 1);
}
function check_parse_parity_no_epsilon(received, expected) {
return parseFloat(received) === parseFloat(expected);
}
function fpu_test_result(v1, v2, textFn, received, expected) {
var result = expected === received,
resultNoRound = check_ignore_round(received, expected),
resultParse = check_parse_parity_no_epsilon(received, expected),
resDiv = document.createElement('div');
resDiv.style.whiteSpace = 'nowrap';
resDiv.style.fontFamily = 'Courier New, Courier, monospace';
resDiv.style.fontSize = '0.74em';
resDiv.style.background = result ? '#aaffaa' : '#ffaaaa';
resDiv.style.borderBottom = 'solid 1px #696969';
resDiv.style.padding = '2px';
resDiv.innerHTML = format(textFn + '(' + v1 + ', ' + v2 + ')', result, resultNoRound, resultParse, received, expected);
document.body.appendChild(resDiv);
(result ? successes : failures).push(resDiv);
return resDiv;
}
function fpu_test_run(v1, v2, addRes, subRes, mulRes, divRes) {
var i, res,
fnLst = [fpu_test_add, fpu_test_sub, fpu_test_mul, fpu_test_div],
fnNam = ['add', 'sub', 'mul', 'div'];
for (i = 0; i < fnLst.length; i++) {
res = fnLst[i].call(null, v1, v2);
fpu_test_result(v1, v2, fnNam[i], res, arguments[i + 2]);
}
}
function setDisplay(s, f) {
var i;
for (i = 0; i < successes.length; i++) {
successes[i].style.display = s;
}
for (i = 0; i < failures.length; i++) {
failures[i].style.display = f;
}
}
var test_header = fpu_test_result('value1', 'value2', 'func', 'received', 'expected'),
test_header_cols = test_header.getElementsByTagName('span');
test_header_cols[1].innerHTML = 'string';
test_header_cols[2].innerHTML = 'rounded';
test_header_cols[3].innerHTML = 'parsed';
test_header.style.background = '#aaaaff';
failures.length = successes.length = 0;
";
private static string summation = @"
var bs = document.createElement('button');
var bf = document.createElement('button');
var ba = document.createElement('button');
bs.innerHTML = 'show successes (' + successes.length + ')';
bf.innerHTML = 'show failures (' + failures.length + ')';
ba.innerHTML = 'show all (' + (successes.length + failures.length) + ')';
ba.style.width = bs.style.width = bf.style.width = '200px';
ba.style.margin = bs.style.margin = bf.style.margin = '4px';
ba.style.padding = bs.style.padding = bf.style.padding = '4px';
bs.onclick = function() { setDisplay('block', 'none'); };
bf.onclick = function() { setDisplay('none', 'block'); };
ba.onclick = function() { setDisplay('block', 'block'); };
document.body.insertBefore(bs, test_header);
document.body.insertBefore(bf, test_header);
document.body.insertBefore(ba, test_header);
document.body.style.minWidth = '700px';
";
private void buttonGenerate_Click(object sender, EventArgs e)
{
var numberOfTests = this.numericNumOfTests.Value;
var strb = new StringBuilder(preamble);
var rand = new Random();
for (int i = 0; i < numberOfTests; i++)
{
double v1 = rand.NextDouble();
double v2 = rand.NextDouble();
strb.Append("fpu_test_run(")
.Append(v1)
.Append(", ")
.Append(v2)
.Append(", '")
.Append(JsEval("" + v1 + '+' + v2))
.Append("', '")
.Append(JsEval("" + v1 + '-' + v2))
.Append("', '")
.Append(JsEval("" + v1 + '*' + v2))
.Append("', '")
.Append(JsEval("" + v1 + '/' + v2))
.Append("');")
.AppendLine();
}
strb.Append(summation);
this.textboxOutput.Text = strb.ToString();
Clipboard.SetText(this.textboxOutput.Text);
}
public Form1()
{
InitializeComponent();
Type evalType = CodeDomProvider
.CreateProvider("JScript")
.CompileAssemblyFromSource(new CompilerParameters(), "package e{class v{public static function e(e:String):String{return eval(e);}}}")
.CompiledAssembly
.GetType("e.v");
this.JsEval = s => (string)evalType.GetMethod("e").Invoke(null, new[] { s });
}
private readonly Func<string, string> JsEval;
}
}
ou une version pré-compilée si vous devez choisir: http://uploading.com/files/ad4a85md/DoubleFloatJs.exe/ c'est un exécutable, télécharger à vos propres risques
Je devrais mentionner que le but du programme est de produire un fichier JavaScript dans une zone de texte et le copier dans le presse-papiers pour des raisons de commodité pour les coller partout où vous le souhaitez, vous pourrait facilement tourner autour et de le mettre sur un asp.net serveur et ajouter de la déclaration de résultats de la commande ping sur le serveur et de garder trace dans certains massifs de la base de données... qui est ce que je ferais si je le désirais l'information..
...et, ...je suis passé ...j'espère que cela vous aide -ck