¿Cómo lidiar con la precisión numérica en Actionscript?

Tengo objetos BigDecimal serializados con BlazeDS en Actionscript. Una vez que accionan Actionscript como objetos Number, tienen valores como:

140475.32 convierte en 140475.31999999999998

¿Cómo trato con esto? El problema es que si uso un NumberFormatter con precisión de 2, entonces el valor se trunca a 140475.31 . ¿Algunas ideas?

Esta es mi solución genérica para el problema (he escrito sobre esto aquí ) :

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

Por ejemplo:

 trace(toFixed(0.12345678, 10)); //0.1 
  • Multiplicar 0.12345678 por 10 ; eso nos da 1.2345678 .
  • Cuando redondeamos 1.2345678 , obtenemos 1.0 ,
  • y finalmente, 1.0 dividido por 10 es igual a 0.1 .

Otro ejemplo:

 trace(toFixed(1.7302394309234435, 10000)); //1.7302 
  • Multiplica 1.7302394309234435 por 10000 ; eso nos da 17302.394309234435 .
  • Cuando redondeamos 17302.394309234435 obtenemos 17302 ,
  • y finalmente, 17302 dividido por 10000 es igual a 1.7302 .


Editar

Basado en la respuesta anónima a continuación , hay una buena simplificación para el parámetro en el método que hace que la precisión sea mucho más intuitiva. p.ej:

 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 

NB He agregado esto aquí por si alguien ve esto como la respuesta y no se desplaza hacia abajo …

Solo una pequeña variación en la función Frasers , para cualquiera que esté interesado.

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

Entonces para usar:

 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 

He utilizado Number.toFixed(precision) en ActionScript 3 para hacer esto: http://livedocs.adobe.com/flex/3/langref/Number.html#toFixed%28%29

maneja el redondeo correctamente y especifica el número de dígitos después del decimal a visualizar, a diferencia de Number.toPrecision() que limita el número total de dígitos para mostrar independientemente de la posición del decimal.

 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)); 

Convertí Java de BigDecimal a ActionScript. No tuvimos opciones ya que calculamos para la aplicación financiera.

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

Puede usar property: rounding = “más cercano”

En NumberFormatter, el redondeo tiene 4 valores que puede elegir: redondear = “ninguno | arriba | abajo | más cercano”. Creo que con su situación, puede elegir redondear = “más cercano”.

– chary –

Descubrí que BlazeDS también admite la serialización de objetos Java BigDecimal en cadenas de ActionScript. Por lo tanto, si no necesita que los datos de ActionScript sean Numbers (no está haciendo ninguna matemática en el lado de Flex / ActionScript), entonces el mapeo String funciona bien (sin rarezas de redondeo). Vea este enlace para ver las opciones de mapeo BlazeDS: http://livedocs.adobe.com/blazeds/1/blazeds_devguide/help.html?content=serialize_data_2.html

GraniteDS 2.2 tiene implementaciones BigDecimal, BigInteger y Long en ActionScript3, opciones de serialización entre Java / Flex para estos tipos, e incluso opciones de herramientas de generación de código para generar variables AS3 de números grandes para las correspondientes Java.

Vea más aquí: http://www.graniteds.org/confluence/display/DOC22/2.+Big+Number+Implementations .

chicos, solo revisen la solución:

             función protegida button1_clickHandler (event: MouseEvent): void
             {
                 formateador var: NumberFormatter = new NumberFormatter ();
                 formateador.precisión = 2;
                 formatter.rounding = NumberBaseRoundType.NEAREST;
                 var a: Number = 14.31999999999998;

                 trace (formatter.format (a));  //14.32
             }

Transmití la implementación de IBM ICU de BigDecimal para el cliente Actionscript. Alguien más ha publicado su versión casi idéntica aquí como un proyecto de código de google. Nuestra versión agrega algunos métodos de conveniencia para hacer comparaciones.

Puede extender el punto final Blaze AMF para agregar soporte de serialización para BigDecimal. Tenga en cuenta que el código en la otra respuesta parece incompleto, y en nuestra experiencia no funciona en producción.

AMF3 supone que los objetos duplicados, los rasgos y las cadenas se envían por referencia. Las tablas de referencia de objeto deben mantenerse sincronizadas durante la serialización, o el cliente perderá la sincronización de estas tablas durante la deserialización y comenzará a lanzar errores de conversión de clase, o corromper los datos en campos que no coinciden, pero que funcionen bien …

Aquí está el código corregido:

 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); } } 

Hay otra forma de enviar un objeto tipeado, que no requiere Externalizable en el cliente. El cliente establecerá la propiedad textValue en el objeto en su lugar:

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

En cualquier caso, su clase Actionscript necesitará esta etiqueta:

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

La clase Actionscript también necesita una propiedad de texto que coincida con la que elijas enviar e inicializará el valor BigDecimal, o en el caso del objeto Externalizable, un par de métodos como este:

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

Este código solo se refiere a los datos que van de un servidor a otro. Para deserializar en la otra dirección de cliente a servidor, elegimos extender AbstractProxy y usar una clase contenedora para almacenar temporalmente el valor de cadena de BigDecimal antes de que se cree el objeto real, debido a que no se puede instanciar un BigDecimal y luego asigne el valor, ya que el diseño de Blaze / LCDS espera que sea el caso con todos los objetos.

Aquí está el objeto proxy para eludir el manejo predeterminado:

 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(); } } 

Esta statement tendrá que ejecutarse en alguna parte de su aplicación, para vincular el objeto proxy a la clase que desea controlar. Usamos un método estático:

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

Nuestra clase de contenedor se ve así:

 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; } } 

Pudimos reutilizar una de las clases disponibles de BigDecimal.as en la web y blazeds extendidos sublimando desde AMF3Output, tendrás que especificar tu propia clase de punto final en los archivos flex xml, en ese extremo personalizado puedes insertar tu propio serializador eso crea una instancia de una subclase 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); } } } 

¡Tan sencillo como eso! entonces tienes soporte BigDecimal nativo usando blazeds, wooohoo! Asegúrate de que tu clase BigDecimal as3 implemente IExternalizable

vítores, jb

Sorprendentemente, la función de ronda en MS Excel nos da valores diferentes a los que ha presentado anteriormente. Por ejemplo en Excel

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

Entonces mi solución para la ronda de Excel es como:

 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); } 

Si conoce la precisión que necesita de antemano, puede almacenar los números escalados para que la cantidad más pequeña que necesita sea un valor total. Por ejemplo, almacene los números como centavos en lugar de dólares.

Si esa no es una opción, ¿qué tal algo así?

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

(Sin embargo, imprime sin decimales atrapados allí).

Sin embargo, esto no funcionará para números realmente grandes, porque aún puede perder precisión.

Puede votar y ver la solicitud de mejora en el sistema de seguimiento de errores Flash Player Jira en https://bugs.adobe.com/jira/browse/FP-3315.

Y mientras tanto, utilice la solución de problemas Number.toFixed (), vea: ( http://livedocs.adobe.com/flex/3/langref/Number.html#toFixed%28%29 )

o utilice las implementaciones de código abierto que existen: ( http://code.google.com/p/bigdecimal/ ) o ( http://www.fxcomps.com/money.html )

En cuanto a los esfuerzos de serialización, bueno, será pequeño si usa Blazeds o LCDS ya que son compatibles con la serialización Java BigDecimal (a String) cf. ( http://livedocs.adobe.com/livecycle/es/sdkHelp/programmer/lcds/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=serialize_data_3.html )

Parece más un problema de transporte, el número es correcto pero se ignora la escala. Si el número debe almacenarse como un BigDecimal en el servidor, es posible que desee convertirlo en un formato menos ambiguo (Número, Doble, Flotante) antes de enviarlo.