Как бороться с точностью числа в ActionScript?
У меня есть объекты BigDecimal, сериализованные с помощью BlazeDS в ActionScript. Как только они нажимают Actionscript как объекты Number, они имеют такие значения, как:
140475.32
превращается в 140475.31999999999998
Как я могу справиться с этим? Проблема в том, что если я использую NumberFormatter с точностью 2, то значение усекается до 140475.31
. Любые идеи?
Ответы
Ответ 1
Это мое общее решение проблемы (я писал об этом здесь):
var toFixed:Function = function(number:Number, factor:int) {
return Math.round(number * factor)/factor;
}
Например:
trace(toFixed(0.12345678, 10)); //0.1
- Умножьте
0.12345678
на 10
; что дает нам 1.2345678
.
- Когда мы округлим
1.2345678
, получим 1.0
,
- и, наконец,
1.0
, деленное на 10
равно 0.1
.
Другой пример:
trace(toFixed(1.7302394309234435, 10000)); //1.7302
- Умножьте
1.7302394309234435
на 10000
; что дает нам 17302.394309234435
.
- Когда мы округлим
17302.394309234435
, получим 17302
,
- и, наконец,
17302
, деленная на 10000
равна 1.7302
.
Edit
Основываясь на анонимном ответе ниже, есть приятное упрощение для параметра в методе, который делает точность более интуитивной. например:
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. Я добавил это здесь на всякий случай, если кто-нибудь увидит это как ответ и не прокрутит вниз...
Ответ 2
Небольшое отклонение от Frasers Функция для всех, кто заинтересован.
function setPrecision(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
Ответ 3
Я использовал Number.toFixed(precision)
в ActionScript 3 для этого: http://livedocs.adobe.com/flex/3/langref/Number.html#toFixed%28%29
он правильно обрабатывает округление и указывает количество цифр после отображения десятичного знака - в отличие от Number.toPrecision()
, которое ограничивает общее количество цифр, отображаемых независимо от положения десятичного числа.
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));
Ответ 4
Я преобразовал Java BigDecimal в ActionScript.
У нас не было выбора, поскольку мы вычисляем для финансового применения.
http://code.google.com/p/bigdecimal/
Ответ 5
Вы можете использовать свойство: rounding = "ближайшее"
В NumberFormatter округление имеет 4 значения, которые вы можете выбрать: rounding = "none | up | down | near". Я думаю, что с вашей ситуацией вы можете выбрать округление = "ближайшее".
- chary -
Ответ 6
Я обнаружил, что BlazeDS поддерживает сериализацию объектов Java BigDecimal для ActionScript Strings. Поэтому, если вам не нужны данные ActionScript для Numbers (вы не выполняете математику на стороне Flex/ActionScript), тогда отображение строк корректно работает (без округления странности). См. Эту ссылку для параметров отображения BlazeDS: http://livedocs.adobe.com/blazeds/1/blazeds_devguide/help.html?content=serialize_data_2.html
Ответ 7
В GraniteDS 2.2 реализованы функции BigDecimal, BigInteger и Long в ActionScript3, параметры сериализации между Java/Flex для этих типов и даже инструменты генерации кода, чтобы генерировать переменные больших чисел AS3 для соответствующих Java.
Подробнее здесь: http://www.graniteds.org/confluence/display/DOC22/2.+Big+Number+Implementations.
Ответ 8
Мы смогли повторно использовать один из доступных классов BigDecimal.as в Интернете и расширенные blazeds с помощью sublassing из AMF3Output, вам нужно указать свой собственный класс конечных точек в файлах flex xml, в этой настраиваемой конечной точке, которую вы можете вставить ваш собственный сериализатор, создающий подкласс 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);
}
}
}
так просто! то у вас есть поддержка BigDecimal, использующая blazeds, wooohoo!
Убедитесь, что ваш класс BigDecimal as3 реализует IExternalizable
cheers, jb
Ответ 9
ребята, просто проверьте решение:
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
}
Ответ 10
Я портировал реализацию IBM ICU BigDecimal для клиента ActionScript. Кто-то еще опубликовал свою почти идентичную версию здесь как проект кода Google. В нашей версии добавлены некоторые удобные методы для проведения сравнений.
Вы можете расширить конечную точку Blaze AMF, чтобы добавить поддержку сериализации для BigDecimal. Обратите внимание, что код в другом ответе кажется неполным, и по нашему опыту он не работает на производстве.
AMF3 предполагает, что дублирующиеся объекты, черты и строки отправляются по ссылке. Таблицы ссылок на объекты должны храниться синхронно во время сериализации или клиент потеряет синхронизацию этих таблиц во время десериализации и начнет бросать вызовы при запуске класса или искажать данные в полях, которые не совпадают, но отличать ok...
Вот скорректированный код:
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);
}
}
Есть еще один способ отправить типизированный объект, который не требует Externalizable на клиенте. Вместо этого клиент установит свойство textValue на объект:
TraitsInfo ti = new TraitsInfo("java.math.BigDecimal",false,false,1);
ti.addProperty("textValue");
writeObjectTraits(ti);
writeObjectProperty("textValue",s);
В любом случае вашему классу ActionScript потребуется этот тег:
[RemoteClass(alias="java.math.BigDecimal")]
Класс ActionScript также нуждается в текстовом свойстве, чтобы соответствовать тому, который вы выбрали для отправки, который инициализирует значение BigDecimal, или в случае объекта Externalizable, несколькими способами:
public function writeExternal(output:IDataOutput):void {
output.writeUTF(this.toString());
}
public function readExternal(input:IDataInput):void {
var s:String = input.readUTF();
setValueFromString(s);
}
Этот код касается только данных, идущих от сервера к клиенту. Чтобы десериализовать в другом направлении от клиента к серверу, мы решили расширить AbstractProxy и использовать класс-оболочку для временного хранения строкового значения BigDecimal до создания фактического объекта из-за того, что вы не можете создать экземпляр BigDecimal, а затем присвойте значение, поскольку дизайн Blaze/LCDS ожидает, что это будет со всеми объектами.
Здесь прокси-объект обходит обработку по умолчанию:
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();
}
}
Этот оператор должен выполнить где-то в вашем приложении, чтобы связать объект-прокси с классом, которым вы хотите управлять. Мы используем статический метод:
PropertyProxyRegistry.getRegistry().register(
java.math.BigDecimal.class, new BigNumberProxy());
Наш класс оболочки выглядит следующим образом:
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;
}
}
Ответ 11
Удивительно, что круговая функция в MS Excel дает нам разные значения, которые вы указали выше.
Например, в Excel
Round(143,355;2)
= 143,36
Итак, мой обходной путь для Excel round похож:
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);
}
Ответ 12
Если вам известна точность, которую вам нужно заранее, вы можете сохранить числа, масштабированные так, чтобы наименьшая сумма, в которой вы нуждаетесь, является целой стоимостью. Например, сохраните цифры как центы, а не доллары.
Если это не вариант, как насчет этого:
function printTwoDecimals(x)
{
printWithNoDecimals(x);
print(".");
var scaled = Math.round(x * 100);
printWithNoDecimals(scaled % 100);
}
(Тем не менее, вы печатаете без запятых.)
Это не будет работать для действительно больших чисел, хотя, потому что вы все равно можете потерять точность.
Ответ 13
Вы можете проголосовать и посмотреть запрос на улучшение в системе отслеживания ошибок Jayer Flash Player на https://bugs.adobe.com/jira/browse/FP-3315
И тем не менее используйте окружение Number.toFixed():
(http://livedocs.adobe.com/flex/3/langref/Number.html#toFixed%28%29)
или используйте реализации с открытым исходным кодом: (http://code.google.com/p/bigdecimal/) или (http://www.fxcomps.com/money.html)
Что касается усилий по сериализации, хорошо, это будет небольшим, если вы используете Blazeds или LCDS, поскольку они поддерживают сериализацию Java BigDecimal (до 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)
Ответ 14
Это больше похоже на транспортную проблему, число правильное, но масштаб игнорируется. Если номер должен быть сохранен как BigDecimal на сервере, вы можете захотеть преобразовать его на стороне сервера в менее двусмысленный формат (Number, Double, Float) перед его отправкой.