Мені завжди казали, що ніколи не представляти гроші double
чи float
типи, і цього разу я ставлю перед вами питання: чому?
Я впевнений, що це дуже вагома причина, я просто не знаю, що це.
Мені завжди казали, що ніколи не представляти гроші double
чи float
типи, і цього разу я ставлю перед вами питання: чому?
Я впевнений, що це дуже вагома причина, я просто не знаю, що це.
Відповіді:
Оскільки поплавці та парні не можуть точно представляти базові 10 кратних, які ми використовуємо для грошей. Ця проблема стосується не лише Java, а будь-якої мови програмування, що використовує базові типи з плаваючою комою.
У базі 10 ви можете записати 10,25 як 1025 * 10 -2 (ціле число разів перевищує потужність 10).Номери IEEE-754 з плаваючою комою різні, але дуже простий спосіб їх думати - це замість цього помножити на два. Наприклад, ви можете дивитись на 164 * 2 -4 (ціле число разів потужність у два), що також дорівнює 10,25. Це не так, як цифри представлені в пам'яті, але математичні наслідки однакові.
Навіть у базі 10 це позначення не може точно представляти більшість простих дробів. Наприклад, ви не можете представляти 1/3: десяткове подання повторюється (0,3333 ...), тому немає кінцевого цілого числа, яке ви можете помножити на потужність 10, щоб отримати 1/3. Ви можете влаштуватися на довгу послідовність 3 та невеликий показник, наприклад 333333333 * 10 -10 , але це не точно: якщо ви помножите це на 3, ви не отримаєте 1.
Однак для підрахунку грошей, принаймні для країн, чиї гроші оцінюються в порядку величини долара США, зазвичай все, що вам потрібно, - це можливість зберігати кратні 10 -2 , тому це насправді не має значення. що 1/3 не може бути представлена.
Проблема з поплавками та дублями полягає в тому, що переважна більшість грошових чисел не мають точного подання як ціле число разів потужність 2. Насправді, єдині кратні 0,01 між 0 і 1 (які є важливими при роботі з грошима, оскільки вони є цілими центами), які можна точно представити як двійкове число з IEEE-754 з плаваючою комою 0, 0,25, 0,5, 0,75 та 1. Всі інші відключаються на невелику суму. Як аналогія з прикладом 0,333333, якщо взяти значення з плаваючою комою на 0,1 і помножити його на 10, ви не отримаєте 1.
Представлення грошей як double
або float
, ймовірно, спочатку буде добре виглядати, коли програмне забезпечення заокруглює крихітні помилки, але, коли ви виконуєте більше додавання, віднімання, множення та ділення на неточні числа, помилки будуть складнішими, і ви отримаєте значні видимі значення не точний. Це робить поплавці та парні недостатні для поводження з грошима, де потрібна ідеальна точність для кратних баз 10 потужностей.
Рішення, яке працює практично на будь-якій мові, полягає в тому, щоб замість цього використовувати цілі числа та рахувати центи. Наприклад, 1025 буде 10,25 доларів. Декілька мов також мають вбудовані типи для боротьби з грошима. Серед інших, Java має BigDecimal
клас, а decimal
тип C # - тип.
1.0 / 10 * 10
може не збігатися з 1,0.
З Bloch, J., Ефективна Java, 2-е видання, пункт 48:
Ці
float
таdouble
типи особливо не підходять для грошових розрахунків, оскільки неможливо представити 0,1 (або будь-яку іншу негативну силу десять) як такуfloat
чиdouble
точну.Наприклад, припустимо, що у вас є 1,03 долара, а ви витрачаєте 42 градуси. Скільки грошей у вас залишилось?
System.out.println(1.03 - .42);
роздруковує
0.6100000000000001
.Правильним способом вирішення цієї проблеми є використання
BigDecimal
,int
абоlong
для грошових розрахунків.
Хоча BigDecimal
є деякі застереження (будь ласка, дивіться прийняту відповідь).
long a = 104
і рахуєте в копіях замість доларів.
BigDecimal
.
Це не питання точності, а також не питання точності. Це питання задоволення очікувань людей, які використовують базу 10 для розрахунків замість бази 2. Наприклад, використання дублів для фінансових обчислень не дає відповідей, які є "неправильними" в математичному сенсі, але це може дати відповіді, які є не те, що очікується у фінансовому сенсі.
Навіть якщо ви округлите свої результати в останню хвилину перед виходом, ви все одно можете час від часу отримувати результат, використовуючи парні, що не відповідають очікуванням.
Використовуючи калькулятор або обчислюючи результати вручну, рівно 1,40 * 165 = 231. Однак, використовуючи подвійний параметр, у моєму середовищі компілятора / операційної системи він зберігається як двійкове число, близьке до 230.99999 ... так що, якщо обрізати число, ви отримаєте 230 замість 231. Ви можете пояснити, що округлення замість обрізання дали бажаний результат 231. Це правда, але округлення завжди передбачає усічення. Яку б техніку округлення ви не використовували, все ж існують такі граничні умови, як ця, яка буде округлятися, коли ви очікуєте, що вона округлятиметься. Вони досить рідкісні, що їх часто не можна знайти шляхом випадкового тестування або спостереження. Можливо, вам доведеться написати якийсь код, щоб шукати приклади, які ілюструють результати, які не ведуть себе так, як очікувалося.
Припустимо, ви хочете щось округлити до найближчої копійки. Таким чином, ви берете свій кінцевий результат, помножуєте на 100, додаєте 0,5, усікаєте, а потім ділите результат на 100, щоб повернутися до копій. Якщо внутрішній номер, який ви зберегли, склав 3,46499999 .... замість 3,465, ви отримаєте 3,46 замість 3,47, коли округлите номер до найближчої копійки. Але ваші базові 10 розрахунків, можливо, вказували на те, що відповідь має бути точно 3,465, що очевидно має становити до 3,47, а не до 3,46. Такі речі трапляються періодично в реальному житті, коли ви використовуєте парні для фінансових розрахунків. Це рідко, тому це часто залишається непоміченим як питання, але це трапляється.
Якщо ви використовуєте базу 10 для своїх внутрішніх обчислень замість подвійних, відповіді завжди є саме те, що очікується від людей, припускаючи, що у вашому коді немає інших помилок.
Math.round(0.49999999999999994)
повертається 1?
Мене турбують деякі з цих відповідей. Я думаю, що парні і плавці мають місце у фінансових розрахунках. Звичайно, при додаванні та відніманні нефракційних грошових сум не буде втрати точності при використанні цілих класів або класів BigDecimal. Але виконуючи більш складні операції, ви часто закінчуєтесь результатами, які виходять з декількох чи багатьох десяткових знаків, незалежно від того, як ви зберігаєте числа. Питання полягає в тому, як ви представляєте результат.
Якщо ваш результат знаходиться на межі між округленням і округленням, і ця остання копійка дійсно має значення, ви, напевно, повинні сказати глядачеві, що відповідь майже посередині - відображаючи більше десяткових знаків.
Проблема з парними, а тим більше з поплавцями, полягає в тому, коли вони використовуються для поєднання великої кількості та невеликої кількості. У java,
System.out.println(1000000.0f + 1.2f - 1000000.0f);
призводить до
1.1875
Поплавці та дублі - приблизні. Якщо ви створите BigDecimal і передасте поплавок в конструктор, ви побачите, що саме float дорівнює:
groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375
можливо, це не так, як ви хочете представляти $ 1,01.
Проблема полягає в тому, що специфікація IEEE не має способу точно представити всі дроби, деякі з них закінчуються як повторювані дроби, так що ви отримуєте помилки наближення. Оскільки бухгалтерам подобається, що речі виходять точно до копійки, а клієнти будуть роздратовані, якщо вони оплатять рахунок і після сплати платежу вони зобов'язані .01, і їм стягується плата або не можуть закрити свій рахунок, краще скористатися точні типи, як десятковий (у C #) або java.math.BigDecimal на Java.
Справа не в тому, що помилка не може бути контрольованою, якщо ви її округляєте: дивіться цю статтю Пітера Лорі . Просто простіше не крутити в першу чергу. Більшість програм, які обробляють гроші, не вимагають великої кількості математики, операції складаються з додавання речей або розподілу сум у різні відра. Введення плаваючої точки і округлення просто ускладнює речі.
float
, double
і BigDecimal
представляють точні значення. Перетворення коду в об'єкт є неточним, як і інші операції. Самі типи не є точними.
Я ризикую, що мене заперечують, але я вважаю, що непридатність цифр з плаваючою комою для розрахунків з валютою завищена. Поки ви переконайтеся, що ви правильно зробите центрисне округлення та маєте достатньо значущих цифр для роботи, щоб протиставити невідповідність двійково-десяткового представлення, пояснену zneak, проблем не виникне.
Люди, що розраховуються з валютою в Excel, завжди використовували поплавці подвійної точності (в Excel немає типу валюти), і я ще не бачив, хто скаржиться на помилки округлення.
Звичайно, ви повинні залишатися в межах розуму; наприклад, простий веб-магазин, мабуть, ніколи не зіткнеться з жодною проблемою з подвійною точністю поплавців, але якщо ви робите, наприклад, облік чи щось інше, що вимагає додавання великої (необмеженої) кількості цифр, ви не хочете торкатися цифр з плаваючою комою десяти футами. полюс.
Хоча це правда, що тип з плаваючою комою може представляти лише приблизні десяткові дані, але також правда, що якщо один округлює числа до необхідної точності перед їх поданням, ви отримуєте правильний результат. Зазвичай.
Зазвичай тому, що подвійний тип має точність менше 16 фігур. Якщо вам потрібна краща точність, це не підходящий тип. Також можуть накопичуватися наближення.
Потрібно сказати, що навіть якщо ви використовуєте арифметику з фіксованою точкою, ви все одно повинні округляти числа, чи не факт, що BigInteger і BigDecimal дають помилки, якщо отримуєте періодичні десяткові числа. Тож є наближення і тут.
Наприклад, COBOL, який історично використовувався для фінансових розрахунків, має максимальну точність 18 цифр. Тому часто відбувається неявне округлення.
Підсумовуючи, на мою думку, дубль непридатний здебільшого за його 16-значну точність, що може бути недостатньою, не тому, що є приблизною.
Розглянемо наступний результат наступної програми. Звідси видно, що після округлення подвійні дають той же результат, що і BigDecimal, до точності 16.
Precision 14
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.000051110111115611
Double : 56789.012345 / 1111111111 = 0.000051110111115611
Precision 15
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.0000511101111156110
Double : 56789.012345 / 1111111111 = 0.0000511101111156110
Precision 16
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.00005111011111561101
Double : 56789.012345 / 1111111111 = 0.00005111011111561101
Precision 17
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.000051110111115611011
Double : 56789.012345 / 1111111111 = 0.000051110111115611013
Precision 18
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.0000511101111156110111
Double : 56789.012345 / 1111111111 = 0.0000511101111156110125
Precision 19
------------------------------------------------------
BigDecimalNoRound : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal : 56789.012345 / 1111111111 = 0.00005111011111561101111
Double : 56789.012345 / 1111111111 = 0.00005111011111561101252
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;
public class Exercise {
public static void main(String[] args) throws IllegalArgumentException,
SecurityException, IllegalAccessException,
InvocationTargetException, NoSuchMethodException {
String amount = "56789.012345";
String quantity = "1111111111";
int [] precisions = new int [] {14, 15, 16, 17, 18, 19};
for (int i = 0; i < precisions.length; i++) {
int precision = precisions[i];
System.out.println(String.format("Precision %d", precision));
System.out.println("------------------------------------------------------");
execute("BigDecimalNoRound", amount, quantity, precision);
execute("DoubleNoRound", amount, quantity, precision);
execute("BigDecimal", amount, quantity, precision);
execute("Double", amount, quantity, precision);
System.out.println();
}
}
private static void execute(String test, String amount, String quantity,
int precision) throws IllegalArgumentException, SecurityException,
IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
Method impl = Exercise.class.getMethod("divideUsing" + test, String.class,
String.class, int.class);
String price;
try {
price = (String) impl.invoke(null, amount, quantity, precision);
} catch (InvocationTargetException e) {
price = e.getTargetException().getMessage();
}
System.out.println(String.format("%-30s: %s / %s = %s", test, amount,
quantity, price));
}
public static String divideUsingDoubleNoRound(String amount,
String quantity, int precision) {
// acceptance
double amount0 = Double.parseDouble(amount);
double quantity0 = Double.parseDouble(quantity);
//calculation
double price0 = amount0 / quantity0;
// presentation
String price = Double.toString(price0);
return price;
}
public static String divideUsingDouble(String amount, String quantity,
int precision) {
// acceptance
double amount0 = Double.parseDouble(amount);
double quantity0 = Double.parseDouble(quantity);
//calculation
double price0 = amount0 / quantity0;
// presentation
MathContext precision0 = new MathContext(precision);
String price = new BigDecimal(price0, precision0)
.toString();
return price;
}
public static String divideUsingBigDecimal(String amount, String quantity,
int precision) {
// acceptance
BigDecimal amount0 = new BigDecimal(amount);
BigDecimal quantity0 = new BigDecimal(quantity);
MathContext precision0 = new MathContext(precision);
//calculation
BigDecimal price0 = amount0.divide(quantity0, precision0);
// presentation
String price = price0.toString();
return price;
}
public static String divideUsingBigDecimalNoRound(String amount, String quantity,
int precision) {
// acceptance
BigDecimal amount0 = new BigDecimal(amount);
BigDecimal quantity0 = new BigDecimal(quantity);
//calculation
BigDecimal price0 = amount0.divide(quantity0);
// presentation
String price = price0.toString();
return price;
}
}
Результат числа плаваючої крапки не є точним, що робить їх непридатними для будь-якого фінансового розрахунку, який вимагає точного результату, а не наближення. float і double призначені для інженерного та наукового розрахунку, і багато разів не дає точного результату, також результат обчислення плаваючої точки може змінюватися від JVM до JVM. Нижче розглянемо приклад BigDecimal та подвійного примітиву, який використовується для відображення грошової вартості, цілком зрозуміло, що обчислення плаваючої точки може бути не точним, і для фінансових розрахунків слід використовувати BigDecimal.
// floating point calculation
final double amount1 = 2.0;
final double amount2 = 1.1;
System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));
// Use BigDecimal for financial calculation
final BigDecimal amount3 = new BigDecimal("2.0");
final BigDecimal amount4 = new BigDecimal("1.1");
System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));
Вихід:
difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9
double
FP до центру не матиме жодних проблем з розрахунком до 0,5 відсотка, як і десятковий FP. Якщо обчислення з плаваючою комою дають значення відсотка, наприклад, 123.499941 ¢, або через двійкову FP, або через десяткову FP, проблема подвійного округлення є однаковою - жодна перевага ні в якому разі. Здається, ваше приміщення передбачає математично точне значення і десятковий FP однакові - щось навіть десяткове FP не гарантує. 0,5 / 7,0 * 7,0 є проблемою для двійкових та деікмальних FP. IAC, більша частина буде суперечкою, оскільки я очікую, що наступна версія C забезпечить десятковий FP.
Як було сказано раніше, "Представлення грошей як подвійне або плаваюче, спочатку, напевно, буде добре виглядати, оскільки програмне забезпечення округляє крихітні помилки, але, коли ви будете робити більше додавання, віднімання, множення та ділення на неточні числа, ви втратите все більше і більше точності як помилки додаються. Це робить поплавці та подвійні недостатніми для роботи з грошима, де потрібна ідеальна точність для кратних баз 10 повноважень ".
Нарешті, у Java є стандартний спосіб роботи з валютою та грошима!
JSR 354: API і гроші та валюти
JSR 354 надає API для представлення, транспортування та проведення комплексних розрахунків за допомогою грошей і валюти. Ви можете завантажити його за посиланням:
JSR 354: Завантажити API грошей та валюти
Специфікація складається з наступних речей:
- API для обробки, наприклад, грошових сум та валют
- API для підтримки взаємозамінних реалізацій
- Фабрики для створення екземплярів класів реалізації
- Функціональність для розрахунків, конвертації та форматування грошових сум
- Java API для роботи з грошима та валютами, який планується включити в Java 9.
- Усі класи специфікацій та інтерфейси розміщені в пакеті javax.money. *.
Приклади прикладів JSR 354: API грошей та валюти:
Приклад створення MonetaryAmount та друку на консолі виглядає приблизно так:
MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
Використовуючи API реалізації посилань, необхідний код набагато простіший:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));
API також підтримує розрахунки з MonetaryAmounts:
MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));
ВалютаУніта та грошова сума
// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);
MonetaryAmount має різні методи, що дозволяють отримати доступ до призначеної валюти, числову суму, її точність та багато іншого:
MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();
int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5
// NumberValue extends java.lang.Number.
// So we assign numberValue to a variable of type Number
Number number = numberValue;
Грошові суми можна округлити за допомогою оператора округлення:
CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35
Під час роботи з колекціями MonetaryAmounts доступні деякі приємні корисні методи фільтрації, сортування та групування.
List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));
Спеціальні операції MonetaryAmount
// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
return Money.of(tenPercent, amount.getCurrency());
};
MonetaryAmount dollars = Money.of(12.34567, "USD");
// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567
Ресурси:
Обробка грошей та валют на Java за допомогою JSR 354
Переглядаючи API 9 грошей та валюти Java 9 (JSR 354)
Див. Також: JSR 354 - Валюта та гроші
Якщо ваші обчислення включають різні етапи, арифметика довільної точності не покриє вас на 100%.
Єдиний надійний спосіб використовувати ідеальне подання результатів (Використовуйте спеціальний тип даних фракції, який буде проводити операції поділу до останнього кроку) та конвертувати лише в десяткові позначення на останньому кроці.
Довільна точність не допоможе, оскільки завжди можуть бути числа з такою кількістю десяткових знаків, або деякі результати, такі як 0.6666666
... Жодне довільне представлення не покриє останній приклад. Так у вас будуть невеликі помилки на кожному кроці.
Ці помилки стануть доповненнями, з часом можуть ігноруватися більше непросто. Це називається Поширення помилок .
Більшість відповідей висвітлюють причини, чому не слід використовувати подвійні гроші для розрахунку грошей та валюти. І я з ними повністю згоден.
Це не означає, що подвійне неможливо використовувати для цієї мети.
Я працював над низкою проектів з дуже низькими вимогами до gc, і наявність об'єктів BigDecimal була великим фактором у цій роботі.
Це нерозуміння щодо подвійного представництва та відсутність досвіду поводження з точністю та точністю, що викликає цю мудру пропозицію.
Ви можете змусити його працювати, якщо вам вдасться впоратися з вимогами точності та точності вашого проекту, що має бути зроблено, виходячи з того, з яким діапазоном подвійних значень має справу.
Щоб отримати більше уявлень, ви можете скористатися методом FuzzyCompare від guava. Допуск параметрів є ключовим. Ми вирішили цю проблему для заявки на торгівлю цінними паперами, і ми провели вичерпне дослідження того, які допуски використовувати для різних числових значень у різних діапазонах.
Також можуть виникнути ситуації, коли ви спокушаєтесь використовувати подвійні обгортки як ключ картки, а хеш-карта є реалізацією. Це дуже ризиковано, тому що Double.equals і хеш-код, наприклад, значення "0,5" та "0,6 - 0,1", спричинить великий безлад.
Багато відповідей на це питання обговорюють IEEE та стандарти навколо арифметики з плаваючою комою.
Виходячи з некомп'ютерних наук (фізика та інженерія), я схильний розглядати проблеми з іншого погляду. Для мене причина, чому я не використовував би подвійний чи плавучий в математичному обчисленні, - це те, що я втратив би занадто багато інформації.
Які альтернативи? Є багато (і ще багато з яких я не знаю!).
BigDecimal на Java є рідною мовою Java. Apfloat - ще одна довільно-точна бібліотека для Java.
Десятковий тип даних у C # є альтернативою Microsoft .NET для 28 значущих цифр.
SciPy (Scientific Python), ймовірно, може також обробляти фінансові розрахунки (я не намагався, але я підозрюю, що так).
Бібліотека множинної точності GNU (GMP) та Бібліотека GNU MFPR - це два безкоштовні та відкриті ресурси для C та C ++.
Існують також бібліотеки з чисельною точністю для JavaScript (!), І я думаю, що PHP може впоратися з фінансовими розрахунками.
Існують також фірмові (зокрема, я думаю, для Fortran) та рішення з відкритим кодом, а також для багатьох мов комп'ютерів.
Я не є інформатиком за навчанням. Однак я схиляюся до BigDecimal на Java або до десяткової у C #. Я не пробував інших перерахованих нами рішень, але вони, мабуть, також дуже хороші.
Для мене мені подобається BigDecimal через методи, які він підтримує. Десятковий знак C # дуже приємний, але я не мав можливості працювати з ним стільки, скільки хотів би. Я займаюся науковими розрахунками, що цікавлять мене у вільний час, і, здається, BigDecimal працює дуже добре, бо можу встановити точність чисел з плаваючою комою. Недолік BigDecimal? Часом це може бути повільно, особливо якщо ви використовуєте метод ділення.
Можливо, для швидкості загляньте у безкоштовні та власні бібліотеки C, C ++ та Fortran.
Щоб додати до попередніх відповідей, є також варіант реалізації Joda-Money в Java, окрім BigDecimal, при вирішенні проблеми, вирішеної в питанні. Назва модуля Java - org.joda.money.
Він вимагає Java SE 8 або новішої версії і не має залежностей.
Якщо точніше, існує залежність часу від компіляції, але вона не потрібна.
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
</dependency>
Приклади використання Joda Money:
// create a monetary value
Money money = Money.parse("USD 23.87");
// add another amount with safe double conversion
CurrencyUnit usd = CurrencyUnit.of("USD");
money = money.plus(Money.of(usd, 12.43d));
// subtracts an amount in dollars
money = money.minusMajor(2);
// multiplies by 3.5 with rounding
money = money.multipliedBy(3.5d, RoundingMode.DOWN);
// compare two amounts
boolean bigAmount = money.isGreaterThan(dailyWage);
// convert to GBP using a supplied rate
BigDecimal conversionRate = ...; // obtained from code outside Joda-Money
Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);
// use a BigMoney for more complex calculations where scale matters
BigMoney moneyCalc = money.toBigMoney();
Документація: http://joda-money.sourceforge.net/apidocs/org/joda/money/Money.html
Приклади реалізації: https://www.programcreek.com/java-api-examples/?api=org.joda.money.Money
Деякі приклади ... це працює (насправді не працює так, як очікувалося) майже на будь-якій мові програмування ... Я намагався з Delphi, VBScript, Visual Basic, JavaScript і тепер з Java / Android:
double total = 0.0;
// do 10 adds of 10 cents
for (int i = 0; i < 10; i++) {
total += 0.1; // adds 10 cents
}
Log.d("round problems?", "current total: " + total);
// looks like total equals to 1.0, don't?
// now, do reverse
for (int i = 0; i < 10; i++) {
total -= 0.1; // removes 10 cents
}
// looks like total equals to 0.0, don't?
Log.d("round problems?", "current total: " + total);
if (total == 0.0) {
Log.d("round problems?", "is total equal to ZERO? YES, of course!!");
} else {
Log.d("round problems?", "is total equal to ZERO? NO... thats why you should not use Double for some math!!!");
}
ВИХІД:
round problems?: current total: 0.9999999999999999
round problems?: current total: 2.7755575615628914E-17
round problems?: is total equal to ZERO? NO... thats why you should not use Double for some math!!!
Float - двійкова форма десяткової форми з різним дизайном; це дві різні речі. Між двома типами помилок при перетворенні один на одного є невеликими. Також поплавок призначений для відображення нескінченної великої кількості значень для наукових. Це означає, що він розрахований на втрату точності до надзвичайно малої та надзвичайно великої кількості з такою фіксованою кількістю байтів. Десяткове число не може представляти нескінченну кількість значень, воно прив'язується до саме такої кількості десяткових цифр. Отже, Float і Decimal призначені для різних цілей.
Є кілька способів управління помилкою щодо вартості валюти:
Використовуйте довге ціле число і рахуйте в копіях замість цього.
Використовуйте подвійну точність, дотримуйтесь лише значущих цифр до 15, щоб десятки могли бути точно змодельовані. Раніше перед поданням значень; Круглі часто, роблячи розрахунки.
Використовуйте десяткову бібліотеку, як Java BigDecimal, тому вам не потрібно використовувати подвійний, щоб імітувати десятковий.
ps цікаво знати, що більшість марок портативних наукових калькуляторів працює на десятковий, а не поплавковий. Тож жодна скарга не плаває помилок перетворення.