How to deal with Number precision in Actionscript? - apache-flex

I have BigDecimal objects serialized with BlazeDS to Actionscript. Once they hit Actionscript as Number objects, they have values like:
140475.32 turns into 140475.31999999999998
How do I deal with this? The problem is that if I use a NumberFormatter with precision of 2, then the value is truncated to 140475.31. Any ideas?

This is my generic solution for the problem (I have blogged about this here):
var toFixed:Function = function(number:Number, factor:int) {
return Math.round(number * factor)/factor;
}
For example:
trace(toFixed(0.12345678, 10)); //0.1
Multiply 0.12345678 by 10; that gives us 1.2345678.
When we round 1.2345678, we get 1.0,
and finally, 1.0 divided by 10 equals 0.1.
Another example:
trace(toFixed(1.7302394309234435, 10000)); //1.7302
Multiply 1.7302394309234435 by 10000; that gives us 17302.394309234435.
When we round 17302.394309234435 we get 17302,
and finally, 17302 divided by 10000 equals 1.7302.
Edit
Based on the anonymous answer below, there is a nice simplification for the parameter on the method that makes the precision much more intuitive. e.g:
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. I added this here just in case anyone sees this as the answer and doesn't scroll down...

Just a slight variation on Frasers Function, for anyone who is interested.
function setPrecision(number:Number, precision:int) {
precision = Math.pow(10, precision);
return (Math.round(number * precision)/precision);
}
So to use:
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

i've used Number.toFixed(precision) in ActionScript 3 to do this: http://livedocs.adobe.com/flex/3/langref/Number.html#toFixed%28%29
it handles rounding properly and specifies the number of digits after the decimal to display - unlike Number.toPrecision() that limits the total number of digits to display regardless of the position of the 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));

I converted the Java of BigDecimal to ActionScript.
We had no choices since we compute for financial application.
http://code.google.com/p/bigdecimal/

You can use property: rounding = "nearest"
In NumberFormatter, rounding have 4 values which you can choice: rounding="none|up|down|nearest". I think with your situation, you can chose rounding = "nearest".
-- chary --

I discovered that BlazeDS supports serializing Java BigDecimal objects to ActionScript Strings as well. So if you don't need the ActionScript data to be Numbers (you are not doing any math on the Flex / ActionScript side) then the String mapping works well (no rounding weirdness). See this link for the BlazeDS mapping options: http://livedocs.adobe.com/blazeds/1/blazeds_devguide/help.html?content=serialize_data_2.html

GraniteDS 2.2 has BigDecimal, BigInteger and Long implementations in ActionScript3, serialization options between Java / Flex for these types, and even code generation tools options in order to generate AS3 big numbers variables for the corresponding Java ones.
See more here: http://www.graniteds.org/confluence/display/DOC22/2.+Big+Number+Implementations.

guys, just check the solution:
protected function button1_clickHandler(event:MouseEvent):void
{
var formatter:NumberFormatter = new NumberFormatter();
formatter.precision = 2;
formatter.rounding = NumberBaseRoundType.NEAREST;
var a:Number = 14.31999999999998;
trace(formatter.format(a)); //14.32
}

I ported the IBM ICU implementation of BigDecimal for the Actionscript client. Someone else has published their nearly identical version here as a google code project. Our version adds some convenience methods for doing comparisons.
You can extend the Blaze AMF endpoint to add serialization support for BigDecimal. Please note that the code in the other answer seems incomplete, and in our experience it fails to work in production.
AMF3 assumes that duplicate objects, traits and strings are sent by reference. The object reference tables need to be kept in sync while serializing, or the client will loose sync of these tables during deserialization and start throwing class cast errors, or corrupting the data in fields that don't match, but cast ok...
Here is the corrected code:
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);
}
}
There is another way to send a typed object, which does not require Externalizable on the client. The client will set the textValue property on the object instead:
TraitsInfo ti = new TraitsInfo("java.math.BigDecimal",false,false,1);
ti.addProperty("textValue");
writeObjectTraits(ti);
writeObjectProperty("textValue",s);
In either case, your Actionscript class will need this tag:
[RemoteClass(alias="java.math.BigDecimal")]
The Actionscript class also needs a text property to match the one you chose to send that will initialize the BigDecimal value, or in the case of the Externalizable object, a couple of methods like this:
public function writeExternal(output:IDataOutput):void {
output.writeUTF(this.toString());
}
public function readExternal(input:IDataInput):void {
var s:String = input.readUTF();
setValueFromString(s);
}
This code only concerns data going from server to client. To deserialize in the other direction from client to server, we chose to extend AbstractProxy, and use a wrapper class to temporarily store the string value of the BigDecimal before the actual object is created, due to the fact that you cannot instantiate a BigDecimal and then assign the value, as the design of Blaze/LCDS expects should be the case with all objects.
Here's the proxy object to circumvent the default handling:
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();
}
}
This statement will have to execute somewhere in your application, to tie the proxy object to the class you want to control. We use a static method:
PropertyProxyRegistry.getRegistry().register(
java.math.BigDecimal.class, new BigNumberProxy());
Our wrapper class looks like this:
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;
}
}

We were able to reuse one of the available BigDecimal.as classes on the web and extended blazeds by sublassing from AMF3Output, you'll need to specify your own endpoint class in the flex xml files, in that custom endpoint you can insert your own serializer that instantiates an AMF3Output subclass.
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);
}
}
}
as simple as that! then you have native BigDecimal support using blazeds, wooohoo!
Make sure your BigDecimal as3 class implements IExternalizable
cheers, jb

Surprisingly the round function in MS Excel gives us different values then you have presented above.
For example in Excel
Round(143,355;2) = 143,36
So my workaround for Excel round is like:
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);
}

If you know the precision you need beforehand, you could store the numbers scaled so that the smallest amount you need is a whole value. For example, store the numbers as cents rather than dollars.
If that's not an option, how about something like this:
function printTwoDecimals(x)
{
printWithNoDecimals(x);
print(".");
var scaled = Math.round(x * 100);
printWithNoDecimals(scaled % 100);
}
(With however you print with no decimals stuck in there.)
This won't work for really big numbers, though, because you can still lose precision.

You may vote and watch the enhancement request in the Flash PLayer Jira bug tracking system at https://bugs.adobe.com/jira/browse/FP-3315
And meanwhile use the Number.toFixed() work-around see :
(http://livedocs.adobe.com/flex/3/langref/Number.html#toFixed%28%29)
or use the open source implementations out there : (http://code.google.com/p/bigdecimal/) or (http://www.fxcomps.com/money.html)
As for the serialization efforts, well, it will be small if you use Blazeds or LCDS as they do support Java BigDecimal serialization (to 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)

It seems more like a transport problem, the number being correct but the scale ignored. If the number has to be stored as a BigDecimal on the server you may want to convert it server side to a less ambiguous format (Number, Double, Float) before sending it.

Related

Haxe: Binding pattern with abstract fields access methods

I'd like to make wrapper to implement simple data binding pattern -- while some data have been modified all registered handlers are got notified. I have started with this (for js target):
class Main {
public static function main() {
var target = new Some();
var binding = new Bindable(target);
binding.one = 5;
// binding.two = 0.12; // intentionally unset field
binding.three = []; // wrong type
binding.four = 'str'; // no such field in wrapped class
trace(binding.one, binding.two, binding.three, binding.four, binding.five);
// outputs: 5, null, [], str, null
trace(target.one, target.two, target.three);
// outputs: 5, null, []
}
}
class Some {
public var one:Int;
public var two:Float;
public var three:Bool;
public function new() {}
}
abstract Bindable<TClass>(TClass) {
public inline function new(source) { this = source; }
#:op(a.b) public function setField<T>(name:String, value:T) {
Reflect.setField(this, name, value);
// TODO notify handlers
return value;
}
#:op(a.b) public function getField<T>(name:String):T {
return cast Reflect.field(this, name);
}
}
So I have some frustrating issues: interface of wrapped object doesn't expose to wrapper, so there's no auto completion or strict type checking, some necessary attributes can be easily omitted or even misspelled.
Is it possible to fix my solution or should I better move to the macros?
I almost suggested here to open an issue regarding this problem. Because some time ago, there was a #:followWithAbstracts meta available for abstracts, which could be (or maybe was?) used to forward fields and call #:op(a.b) at the same time. But that's not really necessary, Haxe is powerful enough already.
abstract Binding<TClass>(TClass) {
public function new(source:TClass) { this = source; }
#:op(a.b) public function setField<T>(name:String, value:T) {
Reflect.setField(this, name, value);
// TODO notify handlers
trace("set: $name -> $value");
return value;
}
#:op(a.b) public function getField<T>(name:String):T {
trace("get: $name");
return cast Reflect.field(this, name);
}
}
#:forward
#:multiType
abstract Bindable<TClass>(TClass) {
public function new(source:TClass);
#:to function to(t:TClass) return new Binding(t);
}
We use here multiType abstract to forward fields, but resolved type is actually regular abstract. In effect, you have completion working and #:op(a.b) called at the same time.
You need #:forward meta on your abstract. However, this will not make auto-completion working unless you remove #:op(A.B) because it shadows forwarded fields.
EDIT: it seems that shadowing happened first time I added #:forward to your abstract, afterwards auto-completion worked just fine.

How to make BlazeDS name conversion to work for properties beginning with a lower-case letter followed by an upper-case one?

I have some trouble with the conversion applied by BlazeDS to the name of the properties when this name begins with a lower-case letter followed by a capital letter.
I have an ActionScript class similar to this:
package as.pkg {
[RemoteClass(alias="java.pkg.Example")]
public class Example {
private var mXRatio:Number;
public function get xRatio():Number {
return mXRatio;
}
public function set xRatio(r:Number):void {
mXRatio = r;
}
}
}
Then I have the equivalent Java class on the server:
package java.pkg;
public class Example {
private Double mXRatio;
public Double getXRatio() {
return mXRatio;
}
public void setXRatio( Double r ) {
mXRatio = r;
}
}
Sending instances from ActionScript to Java works perfectly fine. But when the instances are sent from Java to ActionScript, the following error is displayed:
ReferenceError: Error #1056: Cannot create property XRatio on as.pkg.Example.
Why BlazeDS does not convert the X of XRatio there? How can I avoid this?
BlazeDS uses reflection to inject values into your properties while serializing.
Therefore, your properties (public getter/setter pair or public variable) must have the exact same name or you will get serialization errors like the one you describe above.
Try this and it should be fine:
package as.pkg {
[RemoteClass(alias="java.pkg.Example")]
public class Example {
private var mXRatio:Number;
public function get XRatio():Number { //uppercase X i.s.o lowercase x
return mXRatio;
}
public function set XRatio(r:Number):void { //uppercase X i.s.o lowercase x
mXRatio = r;
}
}
}
Cheers

flex: referencing class variables

I have a bunch of variables in a class. There are situations when I want to set then to null/ "temp" etc as per a well defined logic. The challenge is to list out the variables at multiple places- tedious and error-prone.
classname.speed=NaN
classname.speedtype="not_set"
classname.distance=NaN
classname.distancetype="not_set"
Ideally, would prefer a way to refer to these variables programatically and set something like
"for all class variables- if variable ends in type, set as "not_set"; for other variables set as NaN
How can I achieve this? Any pointers will help
The simplest approach would be just write function to clear them all.
If you want something more automatic, it will requre efforts - look at introspection api. Basically, you call describeType on your class and it returns XML description. All variables will be listed there, along with other info. Then you can parse returned XML and set all variables to needed value, accessing them dynamically with square bracket syntax:
var myClass:MyClass = new MyClass();
myClass["varName"] = "new value";
It can be achieved through Inheritance i.e. implementing interface or extending class
which contains common fields
public class MyClass
{
public a:String = null;
public b:String = null;
public function MyClass()
{
}
}
which contains common var and Child Class could be
public class MyClassChild extends MyClass
{
public var c:String = null;
public function MyClassChild()
{
super();
this.a ="";
this.b ="";
}
}
and you can cast or use for each loop to set values
var temp:MyClassChild = new MyClassChild ();
MyClass(temp).a = "Hello World";
Hopes that helps

Date to String <-> String to Date

I get a Date of my database and I need to show it as a String.
So in Flex I do this:
public static function dateToString(cDate:Date):String {
return cDate.date.toString()+"."+
cDate.month.toString()+"."+
cDate.fullYear.toString()+" "+
cDate.hours.toString()+":"+
cDate.minutes.toString()+":"+
cDate.seconds.toString();
}
But I get for example the result:
13.7.2010 0:0:15
How can I fill the day, month, hours, minutes, seconds with padded 0?
And, I go back from String to Date with:
DateField.stringToDate(myTextInput.text, "DD.MM.YYYY HH:MM:SS");
Is this correct? I want to have a Date which I will transfer via BlazeDS to a J2EE Backend, but I only see in the database then a null value. So something is going wrong...
Best Regards.
Have you seen the DateFormatter class?
Example:
import mx.formatters.DateFormatter;
private var dateFormatter:DateFormatter;
private function init():void
{
dateFormatter = new DateFormatter();
dateFormatter.formatString = 'DD.MM.YYYY HH:NN:SS'
}
public function dateToString(d:Date):String
{
return dateFormatter.format(d);
}
public function stringToDate(s:String):Date
{
return dateFormatter.parseDateString(s);
}
It looks like somebody was asleep the day the wrote Flex 3.2, because DateFormatter::parseDateString is a protected function. It looks like they fixed that by 3.5.
I'm adding this because the stringToDate function does not work on answer above and the simple wrapper doesn't allow you to specify the input string format. The wrapper is actually no longer need since the function is now static, but you still have the same issue. I would recommend instead using the following static function from the DateField Class.
//myObject.CreatedDate = "10022008"
var d:Date = DateField.stringToDate(myObject.CreatedDate, "MMDDYYYY");
You can convert String to Date with DateFormatter::parseDateString, but this method is protected(?). To access method DateFormatter::parseDateString just write a simple wrapper:
import mx.formatters.DateFormatter;
public class DateFormatterWrapper extends DateFormatter
{
public function DateFormatterWrapper()
{
super();
}
public function parseDate(str:String):Date
{
return parseDateString(str);
}
}

Is it possible to define a generic type Vector in Actionsctipt 3?

Hi i need to make a VectorIterator, so i need to accept a Vector with any type. I am currently trying to define the type as * like so:
var collection:Vector.<*> = new Vector<*>()
But the compiler is complaining that the type "is not a compile time constant". i know a bug exists with the Vector class where the error reporting, reports the wrong type as missing, for example:
var collection:Vector.<Sprite> = new Vector.<Sprite>()
if Sprite was not imported, the compiler would complain that it cannot find the Vector class. I wonder if this is related?
So it looks like the answer is there is no way to implicitly cast a Vector of a type to valid super type. It must be performed explicitly with the global Vector.<> function.
So my actual problem was a mix of problems :)
It is correct to use Vector. as a generic reference to another Vector, but, it cannot be performed like this:
var spriteList:Vector.<Sprite> = new Vector.<Sprite>()
var genericList:Vector.<Object> = new Vector.<Object>()
genericList = spriteList // this will cause a type casting error
The assignment should be performed using the global Vector() function/cast like so:
var spriteList:Vector.<Sprite> = new Vector.<Sprite>()
var genericList:Vector.<Object> = new Vector.<Object>()
genericList = Vector.<Object>(spriteList)
It was a simple case of me not reading the documentation.
Below is some test code, I would expect the Vector. to cast implicitly to Vector.<*>.
public class VectorTest extends Sprite
{
public function VectorTest()
{
// works, due to <*> being strictly the same type as the collection in VectorContainer
var collection:Vector.<*> = new Vector.<String>()
// compiler complains about implicit conversion of <String> to <*>
var collection:Vector.<String> = new Vector.<String>()
collection.push("One")
collection.push("Two")
collection.push("Three")
for each (var eachNumber:String in collection)
{
trace("eachNumber: " + eachNumber)
}
var vectorContainer:VectorContainer = new VectorContainer(collection)
while(vectorContainer.hasNext())
{
trace(vectorContainer.next)
}
}
}
public class VectorContainer
{
private var _collection:Vector.<*>
private var _index:int = 0
public function VectorContainer(collection:Vector.<*>)
{
_collection = collection
}
public function hasNext():Boolean
{
return _index < _collection.length
}
public function get next():*
{
return _collection[_index++]
}
}
[Bindable]
public var selectedItems:Vector.<Category>;
public function selectionChange(items:Vector.<Object>):void
{
selectedItems = Vector.<Category>(items);
}
I believe you can refer to an untyped Vector by just calling it Vector (no .<>)
With Apache Flex 4.11.0, you can already do what you want. It might have been there since 4.9.0, but I have not tried that before.
var collection:Vector.<Object> = new Vector.<Object>()
maybe?
But i'm just speculating, haven't tried it.
var collection:Vector.<Object> = new Vector.<Object>()
but only on targeting flash player 10 cs4

Resources