Як боротися з точністю числа з плаваючою комою в JavaScript?


617

У мене є такий сценарій фіктивного тестування:

function test() {
  var x = 0.1 * 0.2;
  document.write(x);
}
test();

Це надрукує результат, 0.020000000000000004тоді як він повинен просто надрукувати 0.02(якщо ви використовуєте калькулятор). Наскільки я зрозумів, це пов'язано з помилками в точності множення плаваючої точки.

Хтось має хороше рішення, щоб у такому випадку я отримав правильний результат 0.02? Я знаю, що є такі функції, як toFixedабо округлення було б ще однією можливістю, але мені хотілося б, щоб ця кількість була надрукована без будь-якого розрізання та округлення. Просто хотів дізнатися, чи є у когось із вас приємне, елегантне рішення.

Звичайно, інакше я обійду приблизно 10 цифр.


118
Власне, помилка полягає в тому, що немає способу зіставити 0.1кінцеве двійкове число з плаваючою точкою.
Аарон Дігулла

10
Більшість дробів неможливо перетворити в десяткову точну точність. Хороше пояснення тут: docs.python.org/release/2.5.1/tut/node16.html
Нейт

7
можливий дублікат Math JavaScript?
epascarello

53
@SalmanA: Те, що час виконання JavaScript приховує цю проблему від вас, не означає, що я помиляюся.
Аарон Дігулла

5
Не погоджуючись з Аароном, існують способи кодування 0,1 досконало та повністю у двійковій формі. Але IEEE 754 не обов'язково визначає це. Уявіть подання, де ви б кодували цілу частину у двійковій, з одного боку, десятковій частині, з іншого, до n десяткових знаків, також у двійковій формі, як звичайне ціле число> 0, і, нарешті, позиція десяткової крапки . Ну, ви б представляли 0,1 ідеально, без помилок. До речі, оскільки JS використовує внутрішню кількість десяткових знаків, вони також можуть закодувати кишки, щоб не помилитися на останніх десяткових колах.
Фабієн Хаддаді

Відповіді:


469

З посібника з плаваючою комою :

Що я можу зробити, щоб уникнути цієї проблеми?

Це залежить від того, які розрахунки ви робите.

  • Якщо вам справді потрібні точно підсумовувати результати, особливо коли ви працюєте з грошима: використовуйте спеціальний десятковий тип даних.
  • Якщо ви просто не хочете бачити всі ці додаткові знаки після коми: просто відформатуйте результат, округлений до фіксованої кількості десяткових знаків, під час його відображення.
  • Якщо у вас немає десяткового типу даних, альтернативою є робота з цілими числами, наприклад, робити розрахунки грошей цілком у центнах. Але це більше роботи і має деякі недоліки.

Зауважте, що перший пункт застосовується лише в тому випадку, якщо вам справді потрібна конкретна точна десяткова поведінка. Більшість людей цього не потребує, вони просто роздратовані тим, що їхні програми не працюють належним чином з числами на зразок 1/10, не розуміючи, що вони навіть не моргнуть при тій самій помилці, якби це сталося з 1/3.

Якщо перший пункт дійсно стосується вас, використовуйте BigDecimal для JavaScript , який зовсім не елегантний, але насправді вирішує проблему, а не надає недосконале рішення.


11
Я помітив ваше мертве посилання на BigDecimal і, шукаючи дзеркало, знайшов альтернативу під назвою BigNumber: jsfromhell.com/classes/bignumber
Jacksonkr

4
@ bass-t: Так, але поплавці можуть точно представляти цілі числа до довжини знаменняі, а відповідно до стандарту ECMA - це 64-бітний поплавок. Тож він може точно представляти цілі числа до 2 ^ 52
Майкл Боргвардт

5
@Karl: Десятковий дріб 1/10 не може бути представлений як кінцевий двійковий дріб у базі 2, і саме це номери Javascript. Так що це насправді точно така ж проблема.
Майкл Боргвардт

12
Я сьогодні дізнався, що навіть цілі числа мають проблеми з точністю у JavaScript. Подумайте, що console.log(9332654729891549)насправді відбитки 9332654729891548(тобто вимкнені одним!)
mlathe

12
@mlathe: Doh .. ;P... Між 2⁵²= 4,503,599,627,370,496і 2⁵³= 9,007,199,254,740,992числа, що представляються, - це саме цілі числа . Для наступного діапазону, від 2⁵³до 2⁵⁴, все множиться на2 , так представимо чисел є навіть ті , і т.д. З іншого боку , для попереднього діапазону від 2⁵¹до 2⁵², дистанціювання 0.5, і т.д. Це пов'язано з простим збільшенням | зменшення бази | radix 2 | бінарний показник в / 64-бітного значення float (що, в свою чергу, пояснює рідко задокументоване "несподіване" поведінку toPrecision()для значень між 0та 1).
GitaarLAB

126

Мені подобається рішення Педро Ладарії та використовую щось подібне.

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

На відміну від рішення Педроса, це округлює 0,999 ... повторення і є точним до плюс / мінус один на найменш значущій цифрі.

Примітка. Під час роботи з 32 або 64 бітовими поплавцями для кращих результатів слід використовувати точність (7) та точність (15). Дивіться це питання для отримання інформації про те, чому.


21
Будь-яка причина, чому ви вибрали 12?
qwertymk

18
toPrecisionповертає рядок замість числа. Це може бути не завжди бажано.
SStanley

7
розбирати Floloat (1.005) .toPrecision (3) => 1.00
Петро

5
@ user2428118, я знаю, я мав намір показати помилку округлення. Результат - 1:00 замість 1,01
Пітер

9
Те, що сказав @ user2428118, може виявитися недостатньо очевидним: (9.99*5).toPrecision(2)= 50 замість 49,95, оскільки точність підраховує ціле число, а не лише десяткові дроби. Потім ви можете використовувати toPrecision(4), але якщо ваш результат дорівнює> 100, то вам знову не вистачає удачі, оскільки це дозволить перші три цифри і один десятковий, що змістить крапку і зробить це більш-менш непридатним. Я toFixed(2)замість цього використовував
aexl

79

Для математично схильних: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Рекомендований підхід полягає у використанні поправочних коефіцієнтів (помножте на відповідну потужність на 10, щоб арифметика траплялася між цілими числами). Наприклад, у випадку 0.1 * 0.2коефіцієнт коригування є 10, і ви виконуєте розрахунок:

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

(Дуже швидке) рішення виглядає приблизно так:

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

В цьому випадку:

> Math.m(0.1, 0.2)
0.02

Я напевно рекомендую використовувати перевірену бібліотеку типу SinfulJS


1
Я люблю це елегантне вирішення, але, здається, не ідеальне: jsfiddle.net/Dm6F5/1 Math.a (76.65, 38.45) повертає 115.10000000000002
nicolallias

3
Math.m (10,2332226616) дає мені "-19627406800", що є негативним значенням ... Я сподіваюся, що повинна бути верхня межа - можливо, це спричиняє цю проблему. Просимо запропонувати
Шива Комуравеллі

1
Це все виглядає чудово, але, здається, десь там є помилка чи дві.
MrYellow

5
Він дуже швидко вирішив, що він сказав ... зламаного виправлення ніхто ніколи не сказав.
Cozzbie

2
Не використовуйте наведений вище код. Це абсолютно не "швидке рішення", якщо воно не працює. Це питання, пов'язане з математикою, тому потрібна точність.
Дренай

49

Ви виконуєте лише множення? Якщо так, то ви можете використовувати для себе корисний секрет про десяткову арифметику. Саме це NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals. Тобто, якщо ми маємо 0.123 * 0.12тоді, ми знаємо, що буде 5 знаків після коми, тому що 0.123це 3 десяткових знаки і 0.12два. Таким чином, якщо JavaScript дав нам таке число, як 0.014760000002ми можемо сміливо округнути до 5-го знаку після коми, не побоюючись втратити точність.


6
... і як отримати точну кількість десяткових знаків.
рядок-о

7
0,5 * 0,2 = 0,10; Ви все одно можете вкорочувати в двох десяткових знаках (або менше). Але ніколи не буде числа, яке має математичне значення поза цим законом.
Нейт Заугг

3
У вас є цитування на це? Також зауважте, що те ж саме не стосується поділу.
Гріффін

3
@NateZaugg ви не можете врізати переливні десяткові знаки, вам потрібно округлити суму, тому що 2090.5 * 8.61 - це 17999.205, але в плаванні
17999.204999999998

3
@Lostfields - Ви праві! Я оновив свою відповідь.
Нейт Заугг

29

Ви шукаєте sprintfреалізацію JavaScript, щоб ви могли виписати поплавці з невеликими помилками в них (оскільки вони зберігаються у двійковому форматі) у очікуваному вами форматі.

Спробуйте javascript-sprintf , ви б назвали це так:

var yourString = sprintf("%.2f", yourNumber);

щоб роздрукувати свій номер як плавець з двома знаками після коми.

Ви також можете використовувати Number.toFixed () для цілей відображення, якщо ви бажаєте не включати більше файлів лише для округлення з плаваючою точкою з заданою точністю.


4
Я думаю, що це найчистіше рішення. Якщо ви дійсно не потребуєте результату 0,02, невелика помилка незначна. Звучить, що важливо - це те, що ваш номер відображається добре, а не у вас є довільна точність.
Довгий Ойянг

2
Для відображення це дійсно найкращий варіант, для складних обчислень перевірити відповідь Боргвардта.
Недоступно

4
Але потім знову повернеться точно такий же рядок, що і вашNumber.toFixed (2).
Роберт

27

Я знаходжу, що BigNumber.js відповідає моїм потребам.

Бібліотека JavaScript для арифметики довільної точності та недесяткової арифметики.

Він має гарну документацію, і автор дуже старанно реагує на відгуки.

У цього ж автора є ще 2 подібних бібліотеки:

Big.js

Невелика, швидка бібліотека JavaScript для арифметики довільної точності. Молодша сестра на bignumber.js.

і Decimal.js

Десятковий тип довільної точності для JavaScript.

Ось код за допомогою BigNumber:

$(function(){

  
  var product = BigNumber(.1).times(.2);  
  $('#product').text(product);

  var sum = BigNumber(.1).plus(.2);  
  $('#sum').text(sum);


});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<!-- 1.4.1 is not the current version, but works for this example. -->
<script src="http://cdn.bootcss.com/bignumber.js/1.4.1/bignumber.min.js"></script>

.1 &times; .2 = <span id="product"></span><br>
.1 &plus; .2 = <span id="sum"></span><br>


3
Використання бібліотеки, безумовно, найкращий вибір на мою думку.
Ентоні

1
З цього посилання github.com/MikeMcl/big.js/isissue/45 bignumber.js -> фінансовий десятичний.js -> науковий big.js -> ???
vee

20
var times = function (a, b) {
    return Math.round((a * b) * 100)/100;
};

--- або ---

var fpFix = function (n) {
    return Math.round(n * 100)/100;
};

fpFix(0.1*0.2); // -> 0.02

--- також ---

var fpArithmetic = function (op, x, y) {
    var n = {
            '*': x * y,
            '-': x - y,
            '+': x + y,
            '/': x / y
        }[op];        

    return Math.round(n * 100)/100;
};

--- як і в ---

fpArithmetic('*', 0.1, 0.2);
// 0.02

fpArithmetic('+', 0.1, 0.2);
// 0.3

fpArithmetic('-', 0.1, 0.2);
// -0.1

fpArithmetic('/', 0.2, 0.1);
// 2

4
Я думаю, що це призведе до тієї ж проблеми, що і результат. Ви повертаєте плаваючу крапку, тому великий шанс, що значення повернення також буде "неправильним".
Герт'ян

1
Дуже розумно і корисно, +1.
Джонатас Уокер

18

Ця функція визначить необхідну точність від множення двох чисел з плаваючою точкою та поверне результат з відповідною точністю. Елегантний, хоча це не так.

function multFloats(a,b){
  var atens = Math.pow(10,String(a).length - String(a).indexOf('.') - 1), 
      btens = Math.pow(10,String(b).length - String(b).indexOf('.') - 1); 
  return (a * atens) * (b * btens) / (atens * btens); 
}

Ew Так, давайте перетворимо числа в рядки для математики з плаваючою комою, і також запропонуємо це як відповідь.
Андрій

16

Дивно, але ця функція ще не була розміщена, хоча інші мають подібні варіанти. Це з веб-документів MDN для Math.round (). Це стисло і дозволяє з різною точністю.

function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

console.log (precisionRound (1234.5678, 1)); // очікуваний вихід: 1234,6

console.log (precisionRound (1234.5678, -1)); // очікуваний вихід: 1230

var inp = document.querySelectorAll('input');
var btn = document.querySelector('button');

btn.onclick = function(){
  inp[2].value = precisionRound( parseFloat(inp[0].value) * parseFloat(inp[1].value) , 5 );
};

//MDN function
function precisionRound(number, precision) {
  var factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}
button{
display: block;
}
<input type='text' value='0.1'>
<input type='text' value='0.2'>
<button>Get Product</button>
<input type='text'>

ОНОВЛЕННЯ: 20 серпня 2019 р. Щойно помітив цю помилку. Я вважаю, що це пов’язано з помилкою точності з плаваючою комою з Math.round ().

precisionRound(1.005, 2) // produces 1, incorrect, should be 1.01

Ці умови працюють правильно:

precisionRound(0.005, 2) // produces 0.01
precisionRound(1.0005, 3) // produces 1.001
precisionRound(1234.5, 0) // produces 1235
precisionRound(1234.5, -1) // produces 1230

Виправити:

function precisionRoundMod(number, precision) {
  var factor = Math.pow(10, precision);
  var n = precision < 0 ? number : 0.01 / factor + number;
  return Math.round( n * factor) / factor;
}

Це просто додає цифру праворуч під час округлення десяткових знаків. MDN оновив сторінку Math.round, щоб, можливо, хтось міг запропонувати краще рішення.


неправильну відповідь. 10.2 завжди поверне 10.19. jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas

@ Žilvinas Посилання, яку ви опублікували в JSBin, не використовує перелічену вище функцію MDN. Я думаю, що ваш коментар спрямований на неправильну людину.
HelloWorldPeace

13

Ви просто повинні вирішити, скільки десяткових цифр ви насправді хочете - не можете мати торт і їсти його теж :-)

Числові помилки накопичуються з кожною подальшою операцією, і якщо ви не вирішите її рано, вона просто зростатиме. Числові бібліотеки, в яких представлені результати, які виглядають чистими, просто відсікають останні два цифри на кожному кроці, чисельні спільні процесори також мають "нормальну" і "повну" довжину з тієї ж причини. Вимкнення манжети є дешевим для процесора, але дуже дорого для вас у сценарії (множення та ділення та використання pov (...)). Хороша математика lib забезпечить підлогу (x, n) для того, щоб зробити обріз для вас.

Отже, принаймні, вам слід зробити глобальний var / константу з pov (10, n) - це означає, що ви визначилися з потрібною точністю :-) Потім зробіть:

Math.floor(x*PREC_LIM)/PREC_LIM  // floor - you are cutting off, not rounding

Ви також можете продовжувати займатися математикою і лише в кінці відрізати - якщо припустити, що ви лише показуєте і не робите if-s з результатами. Якщо ви можете це зробити, то .toFixed (...) може бути ефективнішим.

Якщо ви робите if-s / зіставлення і не хочете скорочувати, вам також потрібна невелика константа, як правило, називається eps, що на один десятковий знак перевищує максимальну очікувану помилку. Скажіть, що ваше відсічення - це останні два десяткових знаки - тоді ваш eps має 1 місце на 3-му місці від останнього (3-й найменший показник), і ви можете використовувати його для порівняння того, чи результат знаходиться в межах очікуваного eps (0,02 -еп <0,1 * 0,2 <0,02 + епс).


Ви також можете додати 0,5, щоб зробити округлення бідолахи: Math.floor (x * PREC_LIM + 0,5) / PREC_LIM
cmroanirgo

Зауважте, що, наприклад, Math.floor(-2.1)є -3. Тому, можливо, використовуйте напр.Math[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
MikeM

Чому floorзамість round?
Квінн Комендант

12

Ви можете використовувати parseFloat()та toFixed()якщо хочете обійти цю проблему для невеликої операції:

a = 0.1;
b = 0.2;

a + b = 0.30000000000000004;

c = parseFloat((a+b).toFixed(2));

c = 0.3;

a = 0.3;
b = 0.2;

a - b = 0.09999999999999998;

c = parseFloat((a-b).toFixed(2));

c = 0.1;

11

Функція round () на phpjs.org прекрасно працює: http://phpjs.org/functions/round

num = .01 + .06;  // yields 0.0699999999999
rnum = round(num,12); // yields 0.07

2
@jrg За умовами, числа, що закінчуються на "5", округляються до найближчого парного (тому що завжди округлення вгору або вниз введене зміщення ваших результатів). Тому 4,725 округлених до двох десяткових знаків дійсно має бути 4,72.
Марк А. Дарем

9

0,6 * 3 це приголомшливо!)) Для мене це прекрасно працює:

function dec( num )
{
    var p = 100;
    return Math.round( num * p ) / p;
}

Дуже дуже просто))


Чи буде це працювати, хоча з чимось подібним 8.22e-8 * 1.3?
Пол Карлтон

0,6 х 3 = 1,8, код ви даєте результати 2 ... так що не добре.
Зйо

@Zyo У цьому випадку він повертає 1,8. Як ти це запустив?
Дренай

Цікаво. Ви можете поміняти місцями операторів множення та ділення в цьому, і це також працює.
Андрій

9

Зауважте, що для загальних цілей використання така поведінка може бути прийнятною.
Проблема виникає при порівнянні цих значень з плаваючою точкою для визначення відповідної дії.
З появою ES6 визначається нова константа Number.EPSILONдля визначення допустимої межі помилки:
Тому замість виконання порівняння, як цього

0.1 + 0.2 === 0.3 // which returns false

ви можете визначити спеціальну функцію порівняння, наприклад:

function epsEqu(x, y) {
    return Math.abs(x - y) < Number.EPSILON;
}
console.log(epsEqu(0.1+0.2, 0.3)); // true

Джерело: http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon


У моєму випадку Number.EPSILON був занадто маленьким, що призвело, наприклад,0.9 !== 0.8999999761581421
Том,

8

Результат, який ви отримали, є правильним та досить послідовним у впровадженні плаваючої точки на різних мовах, процесорах та операційних системах - єдине, що змінюється, - це рівень неточності, коли поплавок насправді подвійний (або вище).

0,1 у двійкових плаваючих точках - це як 1/3 у десятковій частині (тобто 0,3333333333333 ... назавжди), точного способу впоратися з цим просто немає.

Якщо ви маєте справу з поплавцями, завжди очікуйте невеликих помилок округлення, тому вам також доведеться завжди округляти відображений результат до чогось розумного. Натомість ви отримуєте дуже швидку та потужну арифметику, оскільки всі обчислення знаходяться в натурному бінарному процесорі.

Більшість випадків рішення не полягає в тому, щоб переходити на арифметику з фіксованою точкою, головним чином тому, що це набагато повільніше і 99% часу просто не потребує точності. Якщо ви маєте справу з речами, для яких потрібен такий рівень точності (наприклад, фінансові операції) Javascript, мабуть, не найкращий інструмент для використання в будь-якому випадку (як ви хочете застосувати типи фіксованої точки, статична мова, ймовірно, краща ).

Ви шукаєте вишукане рішення, то я боюся, що це: поплавці швидкі, але мають невеликі помилки округлення - завжди круглі до чогось розумного, коли показують свої результати.


8

Щоб уникнути цього, вам слід працювати з цілими значеннями, а не з плаваючими точками. Тож, коли ви хочете мати 2 позиції точності, працюйте зі значеннями * 100, для 3 позицій використовуйте 1000. Для відображення ви використовуєте форматер, щоб помістити в роздільник.

Багато систем опускають роботу з децималами таким чином. Саме тому багато систем працюють з копіями (як цілі числа) замість доларів / євро (як плаваюча точка).


7

Проблема

Плаваюча точка не може точно зберігати всі десяткові значення. Тому при використанні форматів з плаваючою комою завжди будуть помилки округлення на вхідних значеннях. Помилки на входах, звичайно, призводять до помилок на виході. У випадку дискретної функції або оператора можуть бути великі відмінності на виході навколо точки, де функція або оператор є дискретними.

Введення та вихід для значень з плаваючою комою

Отже, використовуючи змінні з плаваючою комою, ви завжди повинні знати про це. І який би результат ви не хочете отримати з розрахунку з плаваючими точками, завжди слід форматувати / кондиціонувати перед тим, як показувати це на увазі.
Якщо використовуються лише безперервні функції та оператори, часто округлення до потрібної точності буде робити (не вкорочувати). Стандартні функції форматування, які використовуються для перетворення плавців у рядок, зазвичай роблять це для вас.
Оскільки округлення додає помилку, яка може призвести до того, що загальна помилка буде більше половини від бажаної точності, вихід повинен бути виправлений на основі очікуваної точності входів та бажаної точності виведення. Ти повинен

  • Круглі введення до очікуваної точності або переконайтесь, що значення не можна вводити з більшою точністю.
  • Додайте невелике значення до результатів перед їх округленням / форматуванням, яке менше або дорівнює 1/4 бажаної точності та більше, ніж максимальна очікувана помилка, викликана помилками округлення на вході та під час обчислення. Якщо це неможливо, поєднання точності використовуваного типу даних недостатньо для забезпечення бажаної точності виводу для вашого обчислення.

Ці 2 речі зазвичай не робляться, і в більшості випадків відмінності, спричинені тим, що не робити їх, занадто малі, щоб бути важливими для більшості користувачів, але у мене вже був проект, коли вихід не приймався користувачами без цих виправлень.

Дискретні функції або оператори (на зразок модуля)

Якщо задіяні дискретні оператори або функції, можуть знадобитися додаткові виправлення, щоб переконатися, що вихід є таким, як очікувалося. Округлення та додавання невеликих виправлень перед округленням не можуть вирішити проблему.
Можливо, потрібна спеціальна перевірка / корекція проміжних результатів обчислення, відразу після застосування дискретної функції або оператора. Для конкретного випадку (оператор модуля) дивіться мою відповідь на питання: Чому оператор модуля повертає дробовий номер у javascript?

Краще уникати проблем

Часто набагато ефективніше уникнути цих проблем, використовуючи типи даних (цілі чи формати фіксованої точки) для таких обчислень, які можуть зберігати очікуваний вхід без помилок округлення. Прикладом цього є те, що ви ніколи не повинні використовувати значення з плаваючою комою для фінансових розрахунків.


4

Погляньте на арифметику з фіксованою точкою . Можливо, це вирішить вашу проблему, якщо діапазон номерів, якими ви хочете оперувати, невеликий (наприклад, валюта). Я би закруглював її до кількох знаків після коми, що є найпростішим рішенням.


5
Проблема - не плаваюча точка проти фіксованої точки, проблема - двійкова проти десяткової.
Майкл Боргвардт

4

Спробуйте мою арифметичну бібліотеку чиліаду, яку ви можете побачити тут . Якщо ви хочете більш пізньої версії, я можу отримати вам її.


4

Ви не можете представляти більшість десяткових дробів саме з двійковими типами з плаваючою комою (це те, що ECMAScript використовує для представлення значень з плаваючою комою). Тож не існує елегантного рішення, якщо ви не використовуєте арифметичні типи довільної точності або тип з плаваючою точкою на основі десяткових знаків. Наприклад, додаток Калькулятор, що постачається з Windows, тепер використовує довільну арифметичну точність для вирішення цієї проблеми .


4

введіть тут опис зображення

    You can use library https://github.com/MikeMcl/decimal.js/. 
    it will   help  lot to give proper solution. 
    javascript console output 95 *722228.630 /100 = 686117.1984999999
    decimal library implementation 
    var firstNumber = new Decimal(95);
    var secondNumber = new Decimal(722228.630);
    var thirdNumber = new Decimal(100);
    var partialOutput = firstNumber.times(secondNumber);
    console.log(partialOutput);
    var output = new Decimal(partialOutput).div(thirdNumber);
    alert(output.valueOf());
    console.log(output.valueOf())== 686117.1985

3

Ви маєте рацію, причиною цього є обмежена точність чисел з плаваючою комою. Зберігайте свої раціональні числа як ділення двох цілих чисел, і в більшості ситуацій ви зможете зберігати числа без втрати точності. Що стосується друку, можливо, ви захочете відобразити результат у вигляді дробу. З запропонованим мною представництвом це стає банальним.

Звичайно, це не дуже допоможе з нераціональними цифрами. Але ви, можливо, захочете оптимізувати свої обчислення таким чином, як вони спричинить найменшу проблему (наприклад, виявлення таких ситуацій, як sqrt(3)^2).


Ви маєте рацію, причина цього є обмеженою точністю чисел з плаваючою точкою - на <pedant>насправді, ОП поклав її на операцію з плаваючою точкою неточною, що неправильно</pedant>
detly

3

У мене була неприємна помилка округлення з модом 3. Іноді, коли я повинен отримати 0, я отримав би .000 ... 01. З цим досить просто впоратися, просто перевірити на <= .01. Але тоді іноді я отримав би 2.99999999999998. ТАКОЖ!

BigNumbers вирішив проблему, але представив ще одну, дещо іронічну, проблему. Коли я намагався завантажити 8,5 в BigNumbers, мені повідомили, що це дійсно 8.4999… і мав понад 15 значущих цифр. Це означало, що BigNumbers не міг її прийняти (я вважаю, я згадав цю проблему дещо іронічно).

Просте рішення іронічної проблеми:

x = Math.round(x*100);
// I only need 2 decimal places, if i needed 3 I would use 1,000, etc.
x = x / 100;
xB = new BigNumber(x);

2

Номер використання (1.234443) .toFixed (2); він надрукує 1,23

function test(){
    var x = 0.1 * 0.2;
    document.write(Number(x).toFixed(2));
}
test();

2

decimal.js , big.js або bignumber.js можуть використовуватися для уникнення проблем з маніпуляцією з плаваючою комою в Javascript:

0.1 * 0.2                                // 0.020000000000000004
x = new Decimal(0.1)
y = x.times(0.2)                          // '0.2'
x.times(0.2).equals(0.2)                  // true

big.js: мінімалістичний; простий у використанні; точність, визначена в десяткових знаках; точність, що застосовується лише до поділу.

bignumber.js: бази 2-64; параметри конфігурації; NaN; Нескінченність; точність, визначена в десяткових знаках; точність, що застосовується лише до поділу; базові префікси.

decimal.js: бази 2-64; параметри конфігурації; NaN; Нескінченність; нецілі повноваження, exp, ln, log; точність, зазначена у значущих цифрах; точність, яка завжди застосовується; випадкові числа.

посилання на детальні порівняння


2

Елегантний, передбачуваний та багаторазовий використання

Давайте розберемося з проблемою елегантним способом багаторазового використання. Наступні сім рядків дозволять отримати доступ до точності з плаваючою точкою, яку ви бажаєте, на будь-яке число, просто додавши .decimalдо кінця числа, формули чи вбудованої Mathфункції.

// First extend the native Number object to handle precision. This populates
// the functionality to all math operations.

Object.defineProperty(Number.prototype, "decimal", {
  get: function decimal() {
    Number.precision = "precision" in Number ? Number.precision : 3;
    var f = Math.pow(10, Number.precision);
    return Math.round( this * f ) / f;
  }
});


// Now lets see how it works by adjusting our global precision level and 
// checking our results.

console.log("'1/3 + 1/3 + 1/3 = 1' Right?");
console.log((0.3333 + 0.3333 + 0.3333).decimal == 1); // true

console.log(0.3333.decimal); // 0.333 - A raw 4 digit decimal, trimmed to 3...

Number.precision = 3;
console.log("Precision: 3");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0.001

Number.precision = 2;
console.log("Precision: 2");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0.01
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 1;
console.log("Precision: 1");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0.1
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Number.precision = 0;
console.log("Precision: 0");
console.log((0.8 + 0.2).decimal); // 1
console.log((0.08 + 0.02).decimal); // 0
console.log((0.008 + 0.002).decimal); // 0
console.log((0.0008 + 0.0002).decimal); // 0

Ура!


2
Якщо ви вирішили брати участь у голосуванні, принаймні наведіть причину.
Бернесто

1

Використовуйте

var x = 0.1*0.2;
 x =Math.round(x*Math.pow(10,2))/Math.pow(10,2);

4
Хм ... але зауважте, що це завжди до двох десятків. Це, звичайно, було б варіантом, але як бути з розрахунком 0,55 * 0,55 (оскільки я не знаю точних цифр заздалегідь. Це дало б 0,3 замість 0,3025. Звичайно, я міг би використати Math.round(x*Math.pow(10,4))/Math.pow(10,4);. Округлення - це завжди варіант, але я просто хотів дізнатися, чи є якесь краще рішення
Юрі,

10.2 завжди повертається 10.19 jsbin.com/tozogiwide/edit?html,js,console,output
Žilvinas


1

Це працює для мене:

function round_up( value, precision ) { 
    var pow = Math.pow ( 10, precision ); 
    return ( Math.ceil ( pow * value ) + Math.ceil ( pow * value - Math.ceil ( pow * value ) ) ) / pow; 
}

round_up(341.536, 2); // 341.54

1
на жаль, round_up (4.15,2) => 4.16.
jrg

1

Вивести за допомогою наступної функції:

var toFixedCurrency = function(num){
    var num = (num).toString();
    var one = new RegExp(/\.\d{1}$/).test(num);
    var two = new RegExp(/\.\d{2,}/).test(num);
    var result = null;

    if(one){ result = num.replace(/\.(\d{1})$/, '.$10');
    } else if(two){ result = num.replace(/\.(\d{2})\d*/, '.$1');
    } else { result = num*100; }

    return result;
}

function test(){
    var x = 0.1 * 0.2;
    document.write(toFixedCurrency(x));
}

test();

Зверніть увагу на вихід toFixedCurrency(x).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.