2017-08-20 18 views
1

J'ai une valeur de float que j'ai besoin de stocker en tant que chaîne en PHP, puis de comparer plus tard après avoir été renvoyé dans un flottant.Marge d'erreur fiable pour Float -> String -> Float Conversion?

En raison de la conversion, je sais que reposant sur l'égalité serait une erreur, car il y a risque d'une perte de précision, donc je fais quelque chose comme ce qui suit:

if (abs((float)$string_value - $float_value) < 0.001) { echo "Values are close enough\n"; } 

Maintenant, alors qu'une marge pour l'erreur de 0.001 devrait être bien pour mes fins immédiates, il m'a fait me demander; Quelle est la plus petite marge d'erreur que je puisse utiliser de manière fiable/sûre? Je me rends compte que la marge d'erreur de sécurité va changer avec la taille du flotteur (c'est-à-dire que les plus grandes valeurs ont moins ou même pas de précision fractionnaire), donc une réponse devrait probablement en tenir compte.

Donc, pour le dire autrement; étant donné une valeur flottante que je veux stocker en base 10 et relire, comment puis-je décider de manière fiable de ma marge d'erreur de manière à pouvoir raisonnablement confirmer que les deux valeurs sont les mêmes?

Malheureusement, les valeurs que je suis la manipulation doivent être stockés sous forme décimale ordinaire, donc mon habitude aller à leur emballage comme ordre réseau entier de 64 bits n'est pas une option ici ☹️

EDIT: Pour clarifier; veuillez supposer que ma question concerne la manipulation de flotteurs de taille arbitraire; l'exemple de code que j'ai donné concerne un cas récent où je gère des flottants dans une plage limitée, donc définir la marge d'erreur manuellement est bien, mais j'aimerais être capable de gérer des flottants de toute magnitude à l'avenir.

+1

Nous avons besoin de plus d'informations sur la conversion en chaîne (et retour). Avec les bonnes conversions, vous n'avez besoin d'aucune marge d'erreur. Par exemple, si vous prenez un nombre à virgule flottante IEEE 754 binary64 fini et que vous le convertissez en décimal en utilisant au moins 17 chiffres significatifs (en utilisant une forme arrondie à la plus proche pour la conversion), vous obtenez une chaîne décimale qui le float _exact_ que vous avez démarré, sans erreur. –

+0

Malheureusement, je pense que je suis limité à la conversion float-to-string par défaut de PHP; Je stocke actuellement la valeur dans memcached à l'heure actuelle, mais cela semble être la façon dont le module memcached de PHP gère les flottants. – Haravikk

Répondre

0

Comme mentionné dans le commentaire de Mark Dickinson, il est possible de convertir un nombre à virgule flottante en une chaîne et retour sans perdre de précision. Cela ne fonctionne que si

  • vous utilisez suffisamment de chiffres décimaux significatifs (17 en double IEEE)
  • les conversions sont exactes (ils sont garantis pour convertir au nombre le plus proche)

D'un regard rapide, il semble que la conversion d'un double $f en une chaîne PHP, implicitement ou avec (string) $f, n'utilise que 14 chiffres significatifs, donc cette méthode n'est pas assez précise. Mais vous pouvez utiliser sprintf avec un spécificateur de conversion %.16e pour obtenir 17 chiffres significatifs. Ainsi, après l'aller-retour suivant

$s = sprintf("%.16e", $f); 
$f2 = (double) $s; 

$f2 doit être égale $f exactement à moins que PHP utilise des algorithmes sous-optimaux en interne.

Notez que le spécificateur de conversion %e utilise la notation scientifique (exponentielle). Si vous avez besoin des chaînes de décimales simples, vous pouvez utiliser le prescripteur %f et calculer le nombre de chiffres après la virgule à l'aide log10:

if ($f != 0) { 
    $prec = 16 - floor(log10(abs($f))); 
    if ($prec < 0) $prec = 0; 
} 
else { 
    $prec = 0; 
} 
$s = sprintf("%.${prec}f", $f); 

Cela peut produire des chaînes extrêmement longues pour un nombre très petits ou grands, cependant.

Il faudrait probablement beaucoup de recherches pour savoir si ces méthodes sont complètement fiables, et sinon quelle est l'erreur maximale. Tout dépend de plusieurs détails d'implémentation comme la version PHP, bibliothèque sous-jacente C, etc.

Une autre idée est de comparer les représentations de chaîne au lieu des valeurs à virgule flottante:

# Assuming $string_value was also converted with float_to_string 
if ($string_value == float_to_string($float_value)) { 
    echo "Values are close enough\n"; 
} 

Cela devrait être fiable aussi longtemps que vous vous en tenez à la même version de PHP.

Si vous devez comparer des nombres à virgule flottante, il est souvent plus logique de comparer l'erreur relative. Voir Bruce Dawson's excellent blog pour plus de détails.