views:

3735

answers:

11

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?

A: 

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.

Jesse Rusak
+9  A: 

I have blogged about this here:

This is my generic solution for the problem.

var toFixed:Function = function(number, factor) {
  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 1.0

divided by 10 equals 0.1.

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 17302 divided by 10000 equals 1.7302

Fraser
Why is that "*=" and not "*"?
Jesse Rusak
I always use the same technique. It works well.
Luke
Very nice! It worked
Mike Sickler
How is this different from the native toFixed function?
grapefrukt
@ grapefrukt - toFixed just gives the same problem in the original question! in that is just gives you the right number of digits with out the correct rounding. Actually toPrecision looks like it but I was unaware that this had been included in AS, I haven't touched it since 2.0!
Fraser
@jder - stylistic. x *= y is x = x * y
Fraser
I would change it to number * factor - the assignment is causing no purpose and is setting a local register for no reason.
Richard Szalay
I made the change...thanks...
Fraser
toPrecision actually returns a string, which means you have to reparse it. Nice one, Adobe!
Richard Szalay
A: 

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.

Theo.T
Java BigDecimal can be converted to AS3 String. BigDecimal is necessary for extremely large numbers: Number doesn't exist in Java; while double and float types are not big enough.
Luis B
+1  A: 

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

+4  A: 

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));
jnichols959
Only sad thing is that it's not localized. You have to use a NumberFormatter if your language uses a comma as the decimal point separator.
Fletch
+1  A: 

I converted the Java of BigDecimal to ActionScript. We had no choices since we compute for financial application.

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

Rouche
+1  A: 

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
Luke Campbell
+1  A: 

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

Mike Hopper
A: 

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)

Francois
A: 

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.

Franck Wolff
A: 

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

jbr