web-dev-qa-db-fra.com

Comment traiter la précision des nombres dans Actionscript?

J'ai des objets BigDecimal sérialisés avec BlazeDS en Actionscript. Une fois qu'ils ont atteint Actionscript en tant qu'objets nombre, ils ont des valeurs telles que:

140475.32 se transforme en 140475.31999999999998

Comment puis-je gérer cela? Le problème est que si j'utilise un NumberFormatter avec une précision de 2, la valeur est tronquée à 140475.31. Des idées?

17
Mike Sickler

C’est ma solution générique au problème (j’ai blogué à ce sujet ici ):

var toFixed:Function = function(number:Number, factor:int) {
  return Math.round(number * factor)/factor;
}

Par exemple:

trace(toFixed(0.12345678, 10)); //0.1
  • Multiplier 0.12345678 par 10; cela nous donne 1.2345678.
  • Lorsque nous arrondissons 1.2345678, nous obtenons 1.0,
  • et finalement, 1.0 divisé par 10 est égal à 0.1.

Un autre exemple:

trace(toFixed(1.7302394309234435, 10000)); //1.7302
  • Multiplier 1.7302394309234435 par 10000; cela nous donne 17302.394309234435.
  • Lorsque nous arrondissons 17302.394309234435, nous obtenons 17302,
  • et finalement, 17302 divisé par 10000 est égal à 1.7302.


Edit

Basé sur la réponse anonyme ci-dessous , il existe une simplification de Nice pour le paramètre de la méthode qui rend la précision beaucoup plus intuitive. par exemple:

var setPrecision:Function = function(number:Number, precision:int) {
 precision = Math.pow(10, precision);
 return Math.round(number * precision)/precision;
}

var number:Number = 10.98813311;
trace(setPrecision(number,1)); //Result is 10.9
trace(setPrecision(number,2)); //Result is 10.98
trace(setPrecision(number,3)); //Result is 10.988 and so on

N.B. J'ai ajouté ceci ici au cas où quelqu'un y verrait la réponse et ne ferait pas défiler vers le bas ...

33
Fraser

Juste une légère variation sur Frasers Fonction, pour tous ceux qui sont intéressés.

function setPrecision(number:Number, precision:int) {
 precision = Math.pow(10, precision);
 return (Math.round(number * precision)/precision);
}

Donc, pour utiliser:

var number:Number = 10.98813311;
trace(setPrecision(number,1)); //Result is 10.9
trace(setPrecision(number,2)); //Result is 10.98
trace(setPrecision(number,3)); //Result is 10.988 and so on
20
user186920

pour ce faire, j'ai utilisé Number.toFixed(precision) dans ActionScript 3: http://livedocs.Adobe.com/flex/3/langref/Number.html#toFixed%28%29

il gère correctement les arrondis et spécifie le nombre de chiffres après la décimale à afficher - contrairement à Number.toPrecision() qui limite le nombre total de chiffres à afficher quelle que soit la position de la décimale.

var roundDown:Number = 1.434;                                             
// will print 1.43                                                        
trace(roundDown.toFixed(2));                                              

var roundUp:Number = 1.436;                                               
// will print 1.44                                                        
trace(roundUp.toFixed(2));                                                
10
jnichols959

J'ai converti le code Java de BigDecimal en ActionScript. Nous n'avions pas le choix, nous calculions pour une application financière.

http://code.google.com/p/bigdecimal/

4
user186673

GraniteDS 2.2 a des implémentations BigDecimal, BigInteger et Long dans ActionScript3, des options de sérialisation entre Java/Flex pour ces types, et même des options pour les outils de génération de code afin de générer des variables de grands nombres AS3 pour les types Java correspondants.

Voir plus ici: http://www.graniteds.org/confluence/display/DOC22/2.+Big+Number+Implementations .

1
Franck Wolff

Nous avons été en mesure de réutiliser l'une des classes BigDecimal.as disponibles sur le Web et nous avons étendu les blazed en sous-classant AMF3Output. Vous devez spécifier votre propre classe de point de terminaison dans les fichiers flex xml. qui instancie une sous-classe AMF3Output.

public class EnhancedAMF3Output extends Amf3Output {

    public EnhancedAMF3Output(final SerializationContext context) {
        super(context);
    }

    public void writeObject(final Object o) throws IOException {           
        if (o instanceof BigDecimal) {
            write(kObjectType);
            writeUInt29(7); // write U290-traits-ext (first 3 bits set)
            writeStringWithoutType("Java.math.BigDecimal");
            writeAMFString(((BigDecimal)o).toString());
        } else {
            super.writeObject(o);
        }
    }
}

aussi simple que cela! alors vous avez le support natif BigDecimal en utilisant blazeds, wooohoo! Assurez-vous que votre classe BigDecimal as3 implémente IExternalizable

à la vôtre, jb

1
jbr

les gars, il suffit de vérifier la solution:

 protected function button1_clickHandler (event: MouseEvent): void 
 {
 var formateur: NumberFormatter = new NumberFormatter (); 
 formateur.précision = 2; 
 formatter.rounding = NumberBaseRoundType.NEAREST; 
 var a: Number = 14.31999999999998; 
 
 trace (formateur.format (a)); //14.32
} 
1
user578737

Vous pouvez utiliser la propriété: arrondi = "le plus proche"

Dans NumberFormatter, les arrondis ont 4 valeurs que vous pouvez choisir: arrondi = "aucun | en haut | en bas | le plus proche". Je pense qu'avec votre situation, vous pouvez choisir l'arrondi = "le plus proche".

- chary -

1
chary1112004

J'ai porté l'implémentation IBM ICU de BigDecimal pour le client Actionscript. Quelqu'un d'autre a publié sa version presque identique ici en tant que projet de code Google. Notre version ajoute des méthodes pratiques pour effectuer des comparaisons.

Vous pouvez étendre le point de terminaison Blaze AMF pour ajouter la prise en charge de la sérialisation pour BigDecimal. Veuillez noter que le code dans l’autre réponse semble incomplet et que, selon notre expérience, il ne fonctionne pas en production. 

AMF3 suppose que les objets, traits et chaînes en double sont envoyés par référence. Les tables de référence d'objet doivent être synchronisées pendant la sérialisation, sinon le client perdra la synchronisation pendant la désérialisation et commencera à jeter des erreurs de conversion de classe ou à corrompre les données dans des champs qui ne correspondent pas, mais qui lancent ok ...

Voici le code corrigé:

public void writeObject(final Object o) throws IOException {
    if (o instanceof BigDecimal) {
        write(kObjectType);
        if(!byReference(o)){   // if not previously sent
            String s = ((BigDecimal)o).toString();                  
            TraitsInfo ti = new TraitsInfo("Java.math.BigDecimal",false,true,0);
            writeObjectTraits(ti); // will send traits by reference
            writeUTF(s);
            writeObjectEnd();  // for your AmfTrace to be correctly indented
        }
    } else {
            super.writeObject(o);
        }
}

Il existe un autre moyen d’envoyer un objet typé, qui ne nécessite pas Externalizable sur le client. Le client définira la propriété textValue sur l'objet à la place:

TraitsInfo ti = new TraitsInfo("Java.math.BigDecimal",false,false,1);           
ti.addProperty("textValue");
writeObjectTraits(ti);
writeObjectProperty("textValue",s);

Dans les deux cas, votre classe Actionscript aura besoin de cette balise:

[RemoteClass(alias="Java.math.BigDecimal")]

La classe Actionscript a également besoin d'une propriété text correspondant à celle que vous avez choisi d'envoyer et initialisant la valeur BigDecimal, ou, dans le cas de l'objet Externalizable, de deux méthodes telles que:

public  function writeExternal(output:IDataOutput):void {       
    output.writeUTF(this.toString());
}
public  function readExternal(input:IDataInput):void {          
    var s:String = input.readUTF();
    setValueFromString(s);
}

Ce code concerne uniquement les données allant du serveur au client. Pour désérialiser dans l'autre sens client à serveur, nous avons choisi d'étendre AbstractProxy et d'utiliser une classe wrapper pour stocker temporairement la valeur de chaîne de BigDecimal avant la création de l'objet réel, car vous ne pouvez pas instancier BigDecimal, puis attribuez la valeur, comme prévu par la conception de Blaze/LCDS dans tous les objets.

Voici l'objet proxy pour contourner le traitement par défaut:

public class BigNumberProxy extends AbstractProxy {

    public BigNumberProxy() {
        this(null);
    }

    public BigNumberProxy(Object defaultInstance) {
        super(defaultInstance);
        this.setExternalizable(true);

        if (defaultInstance != null)
           alias = getClassName(defaultInstance);
    }   

    protected String getClassName(Object instance) {
        return((BigNumberWrapper)instance).getClassName();
    }

    public Object createInstance(String className) {
        BigNumberWrapper w = new BigNumberWrapper();
        w.setClassName(className);
        return w;
    }

    public Object instanceComplete(Object instance) {
    String desiredClassName = ((BigNumberWrapper)instance).getClassName();
    if(desiredClassName.equals("Java.math.BigDecimal"))
        return new BigDecimal(((BigNumberWrapper)instance).stringValue);
    return null;
}

    public String getAlias(Object instance) {
        return((BigNumberWrapper)instance).getClassName();
    }

}

Cette instruction devra être exécutée quelque part dans votre application pour lier l'objet proxy à la classe que vous souhaitez contrôler. Nous utilisons une méthode statique:

PropertyProxyRegistry.getRegistry().register(
    Java.math.BigDecimal.class, new BigNumberProxy());

Notre classe wrapper ressemble à ceci:

public class BigNumberWrapper implements Externalizable {

    String stringValue;
    String className;

    public void readExternal(ObjectInput arg0) throws IOException, ClassNotFoundException {
        stringValue = arg0.readUTF();       
    }

    public void writeExternal(ObjectOutput arg0) throws IOException {
        arg0.writeUTF(stringValue);     
    }

    public String getStringValue() {
        return stringValue;
    }

    public void setStringValue(String stringValue) {
        this.stringValue = stringValue;
    }

    public String getClassName() {
        return className;
    }

    public void setClassName(String className) {
        this.className = className;
    }

}
1
busitech

Étonnamment, la fonction round dans MS Excel nous donne des valeurs différentes de celles que vous avez présentées ci-dessus. Par exemple, dans Excel

Round(143,355;2) = 143,36

Donc, ma solution de contournement pour Excel est la suivante:

public function setPrecision(number:Number, precision:int):Number {
precision = Math.pow(10, precision);

const excelFactor : Number = 0.00000001;

number += excelFactor;

return (Math.round(number * precision)/precision);
}
1
Adam Adamczyk

J'ai découvert que BlazeDS prend également en charge la sérialisation des objets Java BigDecimal dans des chaînes ActionScript. Par conséquent, si vous n'avez pas besoin que les données ActionScript soient des nombres (vous ne faites pas de calculs du côté Flex/ActionScript), le mappage de chaîne fonctionne bien (aucune bizarrerie d'arrondi). Voir ce lien pour les options de mappage BlazeDS: http://livedocs.Adobe.com/blazeds/1/blazeds_devguide/help.html?content=serialize_data_2.html

1
Mike Hopper

Si vous connaissez la précision dont vous avez besoin au préalable, vous pouvez stocker les nombres mis à l'échelle de sorte que le plus petit montant dont vous avez besoin soit une valeur entière. Par exemple, stockez les nombres en cents plutôt qu'en dollars.

Si ce n'est pas une option, pourquoi pas quelque chose comme ceci: 

function printTwoDecimals(x)
{
   printWithNoDecimals(x);
   print(".");
   var scaled = Math.round(x * 100);
   printWithNoDecimals(scaled % 100);
}

(Avec cependant, vous imprimez sans décimale coincée dedans.)

Cela ne fonctionnera pas pour vraiment grands nombres, cependant, car vous pouvez toujours perdre en précision. 

0
Jesse Rusak

Vous pouvez voter et regarder la demande d'amélioration dans le système de suivi des bogues Jira de Flash Player à https://bugs.Adobe.com/jira/browse/FP-3315

Et entre-temps, utilisez l’entraînement Number.toFixed (), voir: ( http://livedocs.Adobe.com/flex/3/langref/Number.html#toFixed%28%29 )

ou utilisez les implémentations open source disponibles: ( http://code.google.com/p/bigdecimal/ ) ou ( http://www.fxcomps.com/money.html

En ce qui concerne les efforts de sérialisation, eh bien, ce sera peu si vous utilisez Blazeds ou LCDS car ils supportent la sérialisation BigDecimal Java (to String) cf. ( http://livedocs.Adobe.com/livecycle/es/sdkHelp/programmer/lcds/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=serialize_data_data_3.html )

0
Francois