Làm thế nào để đối phó với độ chính xác số dấu phẩy động trong JavaScript?


619

Tôi có kịch bản kiểm tra giả sau đây:

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

Điều này sẽ in kết quả 0.020000000000000004trong khi nó sẽ chỉ in 0.02(nếu bạn sử dụng máy tính của bạn). Theo tôi hiểu điều này là do lỗi trong độ chính xác của phép nhân dấu phẩy động.

Có ai có một giải pháp tốt để trong trường hợp như vậy tôi nhận được kết quả chính xác 0.02? Tôi biết có những chức năng như toFixedhoặc làm tròn sẽ là một khả năng khác, nhưng tôi muốn thực sự có toàn bộ số được in mà không cần cắt và làm tròn. Chỉ muốn biết nếu một trong các bạn có một số giải pháp tốt đẹp, thanh lịch.

Tất nhiên, nếu không tôi sẽ làm tròn đến khoảng 10 chữ số.


118
Trên thực tế, lỗi là do không có cách nào để ánh xạ 0.1tới một số dấu phẩy động nhị phân hữu hạn.
Aaron Digulla

10
Hầu hết các phân số không thể được chuyển đổi thành số thập phân với độ chính xác chính xác. Một lời giải thích tốt có ở đây: docs.python.org/release/2.5.1/tut/node16.html
Nate Zaugg


53
@SalmanA: Rằng thời gian chạy JavaScript của bạn che giấu vấn đề này khỏi bạn không có nghĩa là tôi sai.
Aaron Digulla

5
Không đồng ý với Aaron, có nhiều cách để mã 0,1 hoàn hảo và hoàn toàn trong hệ nhị phân. Nhưng IEEE 754 không nhất thiết phải định nghĩa điều này. Hãy tưởng tượng một đại diện nơi bạn sẽ mã hóa phần nguyên ở dạng nhị phân một mặt, mặt khác là phần thập phân, tối đa n số thập phân, trong nhị phân, giống như một số nguyên bình thường> 0, và cuối cùng, vị trí của dấu thập phân . Vâng, bạn sẽ đại diện cho 0,1 hoàn hảo, không có lỗi. Btw, vì JS sử dụng số thập phân hữu hạn trong nội bộ, nên các nhà phát triển cũng có thể mã hóa can đảm để không phạm sai lầm đó vào các số thập phân cuối cùng.
Fabien Haddadi

Câu trả lời:


469

Từ Hướng dẫn Điểm nổi :

Tôi có thể làm gì để tránh vấn đề này?

Điều đó phụ thuộc vào loại tính toán bạn đang làm.

  • Nếu bạn thực sự cần kết quả của mình để cộng chính xác, đặc biệt là khi bạn làm việc với tiền: hãy sử dụng kiểu dữ liệu thập phân đặc biệt.
  • Nếu bạn không muốn xem tất cả các vị trí thập phân thêm đó: chỉ cần định dạng kết quả của bạn được làm tròn thành một số vị trí thập phân cố định khi hiển thị nó.
  • Nếu bạn không có kiểu dữ liệu thập phân có sẵn, một giải pháp thay thế là làm việc với các số nguyên, ví dụ: tính toán tiền hoàn toàn bằng xu. Nhưng đây là công việc nhiều hơn và có một số nhược điểm.

Lưu ý rằng điểm đầu tiên chỉ áp dụng nếu bạn thực sự cần hành vi thập phân chính xác cụ thể . Hầu hết mọi người không cần điều đó, họ chỉ cáu kỉnh rằng các chương trình của họ không hoạt động chính xác với các số như 1/10 mà không nhận ra rằng họ thậm chí sẽ không chớp mắt với cùng một lỗi nếu xảy ra với 1/3.

Nếu điểm đầu tiên thực sự áp dụng cho bạn, hãy sử dụng BigDecimal cho JavaScript , điều này không thanh lịch chút nào, nhưng thực sự giải quyết vấn đề thay vì cung cấp một cách giải quyết không hoàn hảo.


11
Tôi nhận thấy liên kết chết của bạn cho BigDecimal và trong khi tìm kiếm một chiếc gương, tôi đã tìm thấy một thay thế có tên là BigNumber: jsfromhell.com/groupes/bignumber
Jacksonkr

4
@ bass-t: Có, nhưng số float có thể biểu thị chính xác các số nguyên cho đến độ dài của ý nghĩa và theo tiêu chuẩn ECMA, đó là số float 64 bit. Vì vậy, nó chính xác có thể đại diện cho số nguyên lên tới 2 ^ 52
Michael Borgwardt

5
@Karl: Phân số thập phân 1/10 không thể được biểu diễn dưới dạng phân số nhị phân hữu hạn trong cơ sở 2 và đó là số Javascript. Vì vậy, nó trong thực tế chính xác cùng một vấn đề.
Michael Borgwardt

12
Hôm nay tôi đã học được rằng ngay cả số nguyên cũng có vấn đề chính xác trong javascript. Hãy xem xét rằng console.log(9332654729891549)thực sự in 9332654729891548(tức là tắt bởi một!)
mledit

12
@mledit: Doh .. ;P... Giữa 2⁵²= 4,503,599,627,370,4962⁵³= 9,007,199,254,740,992các số có thể biểu diễn chính xác là các số nguyên . Đối với phạm vi tiếp theo, từ 2⁵³đến 2⁵⁴, mọi thứ đều được nhân với2 , do đó, các số có thể biểu thị là số chẵn , v.v ... Ngược lại, đối với phạm vi trước đó từ 2⁵¹đến 2⁵², khoảng cách là 0.5, v.v. Điều này là do đơn giản là tăng | giảm cơ sở | radix 2 | số mũ nhị phân trong / của giá trị float 64 bit (lần lượt giải thích hành vi 'bất ngờ' hiếm khi được ghi lại của toPrecision()các giá trị giữa 01).
GitaarLAB

126

Tôi thích giải pháp của Pedro Ladaria và sử dụng một cái gì đó tương tự.

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

Không giống như giải pháp Pedros, nó sẽ làm tròn 0,999 ... lặp lại và chính xác để cộng / trừ một trên chữ số có nghĩa ít nhất.

Lưu ý: Khi xử lý các float 32 hoặc 64 bit, bạn nên sử dụng toPrecision (7) và toPrecision (15) để có kết quả tốt nhất. Xem câu hỏi này để biết thông tin tại sao.


21
Bất kỳ lý do tại sao bạn chọn 12?
qwertymk

18
toPrecisiontrả về một chuỗi thay vì một số. Điều này có thể không phải luôn luôn là mong muốn.
SStanley

7
parseFloat (1.005) .toPrecision (3) => 1.00
Peter

5
@ user2428118, tôi biết, ý tôi là hiển thị lỗi làm tròn, Kết quả là 1,00 thay vì 1,01
Peter

9
Những gì @ user2428118 nói có thể không đủ rõ ràng: (9.99*5).toPrecision(2)= 50 thay vì 49,95 vì toPrecision đếm toàn bộ số, không chỉ là số thập phân. Sau đó toPrecision(4), bạn có thể sử dụng , nhưng nếu kết quả của bạn> 100 thì bạn lại gặp may, vì nó sẽ cho phép ba số đầu tiên và một số thập phân, theo cách đó thay đổi dấu chấm và khiến cho số này ít nhiều không sử dụng được. Tôi đã kết thúc sử dụng toFixed(2)thay vì
aexl

79

Đối với các khuynh hướng toán học: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Cách tiếp cận được đề xuất là sử dụng các hệ số hiệu chỉnh (nhân với công suất phù hợp là 10 để số học xảy ra giữa các số nguyên). Ví dụ: trong trường hợp 0.1 * 0.2, hệ số hiệu chỉnh là 10và bạn đang thực hiện phép tính:

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

Một giải pháp (rất nhanh) trông giống như:

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); };

Trong trường hợp này:

> Math.m(0.1, 0.2)
0.02

Tôi chắc chắn khuyên bạn nên sử dụng một thư viện được thử nghiệm như SinfulJS


1
Il thích cách giải quyết tao nhã này nhưng dường như không hoàn hảo: jsfiddle.net/Dm6F5/1 Math.a (76,65, 38,45) trả về 115.10000000000002
nicolallias

3
Math.m (10,2332226616) đang cho tôi "-19627406800" là giá trị âm ... Tôi hy vọng phải có giới hạn trên - có thể gây ra vấn đề này. Xin đề nghị
Shiva Komuravelly

1
Tất cả điều này có vẻ tuyệt vời, nhưng dường như có một hoặc hai sai lầm ở đó ở đâu đó.
MrYellow

5
Giải pháp rất nhanh anh nói ... sửa lỗi không ai từng nói.
Cozzbie

2
Đừng sử dụng đoạn mã trên. Đó hoàn toàn không phải là "giải pháp nhanh" nếu nó không hoạt động. Đây là một câu hỏi liên quan đến toán học, vì vậy cần có độ chính xác.
Drenai

49

Bạn chỉ thực hiện phép nhân? Nếu vậy thì bạn có thể sử dụng lợi thế của mình một bí mật gọn gàng về số học thập phân. Đó là điều đó NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals. Điều đó có nghĩa là nếu chúng ta có 0.123 * 0.12thì chúng ta biết rằng sẽ có 5 chữ số thập phân vì 0.123có 3 chữ số thập phân và 0.12có hai chữ số thập phân . Do đó, nếu JavaScript cho chúng ta một số như 0.014760000002chúng ta có thể làm tròn một cách an toàn đến vị trí thập phân thứ 5 mà không sợ mất độ chính xác.


6
... Và làm thế nào để có được số lượng chính xác của số thập phân.
line-o

7
0,5 * 0,2 = 0,10; Bạn vẫn có thể cắt ngắn ở 2 vị trí thập phân (hoặc ít hơn). Nhưng sẽ không bao giờ có một con số với bất kỳ ý nghĩa toán học nào ngoài luật này.
Nate Zaugg

3
Bạn có một trích dẫn cho điều này? Cũng lưu ý rằng điều tương tự là không đúng cho phân chia.
Griffin

3
@NateZaugg bạn không thể cắt bớt số thập phân tràn, bạn phải làm tròn số tiền, bởi vì 2090,5 * 8,61 là 17999.205 nhưng trong số đó là 17999.204999999998
Lostfields

3
@Lostfields - Bạn đúng rồi! Tôi đã cập nhật câu trả lời của tôi.
Nate Zaugg

29

Bạn đang tìm kiếm một sprintftriển khai cho JavaScript, để bạn có thể viết ra các float với các lỗi nhỏ trong đó (vì chúng được lưu trữ ở định dạng nhị phân) theo định dạng mà bạn mong đợi.

Hãy thử javascript-sprintf , bạn sẽ gọi nó như thế này:

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

để in ra số của bạn dưới dạng nổi với hai chữ số thập phân.

Bạn cũng có thể sử dụng Number.toFixed () cho mục đích hiển thị, nếu bạn không muốn bao gồm nhiều tệp hơn chỉ để làm tròn điểm nổi với độ chính xác nhất định.


4
Tôi nghĩ rằng đây là giải pháp sạch nhất. Trừ khi bạn thực sự cần kết quả là 0,02, lỗi nhỏ là không đáng kể. Có vẻ như điều quan trọng là số của bạn được hiển thị độc đáo, không phải là bạn có độ chính xác tùy ý.
Long Ouyang

2
Để hiển thị, đây thực sự là lựa chọn tốt nhất, đối với các phép tính phức tạp, hãy kiểm tra câu trả lời của Borgwardt.
Không có sẵn

4
Nhưng sau đó, điều này sẽ trả về chính xác chuỗi tương tự như yourNumber.toFixed (2).
Robert

27

Tôi đang tìm BigNumber.js đáp ứng nhu cầu của tôi.

Một thư viện JavaScript cho số học thập phân và không thập phân chính xác tùy ý.

Nó có tài liệu tốt và tác giả rất siêng năng trả lời phản hồi.

Cùng một tác giả có 2 thư viện tương tự khác:

Big.js

Một thư viện JavaScript nhỏ, nhanh cho số học thập phân chính xác tùy ý. Các em gái đến bignumber.js.

Decimal.js

Một kiểu thập phân chính xác tùy ý cho JavaScript.

Đây là một số mã sử dụng 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
Sử dụng một thư viện chắc chắn là sự lựa chọn tốt nhất theo ý kiến ​​của tôi.
Anthony

1
Từ liên kết này github.com/MikeMcl/big.js/issues/45 bignumber.js -> tài chính thập phân.js -> khoa học big.js -> ???
vee

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

---hoặc là---

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

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

---cũng thế---

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

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

--- như trong ---

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
Tôi nghĩ rằng điều đó sẽ cho cùng một vấn đề như là kết quả. Bạn trả về một dấu phẩy động nên rất có thể giá trị trả về cũng sẽ "không chính xác".
Gertjan

1
Rất thông minh và hữu ích, +1.
Jonatas Walker

18

Hàm này sẽ xác định độ chính xác cần thiết từ phép nhân hai số dấu phẩy động và trả về kết quả với độ chính xác phù hợp. Thanh lịch mặc dù nó không phải là.

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. Có, hãy chuyển đổi số thành chuỗi cho toán học dấu phẩy động và cũng đề xuất đó là câu trả lời.
Andrew

17

Đáng ngạc nhiên, chức năng này chưa được đăng tải mặc dù những người khác có các biến thể tương tự của nó. Đó là từ các tài liệu web MDN cho Math.round (). Nó súc tích và cho phép thay đổi độ chính xác.

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

console.log (precisionRound (1234.5678, 1)); // sản lượng dự kiến: 1234.6

console.log (precisionRound (1234.5678, -1)); // sản lượng dự kiến: 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'>

CẬP NHẬT: Ngày 20 tháng 8 năm 2019 Chỉ cần nhận thấy lỗi này. Tôi tin rằng đó là do lỗi chính xác của dấu phẩy động với Math.round ().

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

Những điều kiện này hoạt động chính xác:

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

Sửa chữa:

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

Điều này chỉ cần thêm một chữ số ở bên phải khi làm tròn số thập phân. MDN đã cập nhật trang Math.round để có thể ai đó có thể cung cấp giải pháp tốt hơn.


câu trả lời sai. 10.2 sẽ luôn trả về 10.19. jsbin.com/tozogi Worldwide /edit? html,js,console,output
ilvinas

@ Ilvinas Liên kết JSBin bạn đã đăng không sử dụng chức năng MDN được liệt kê ở trên. Tôi nghĩ rằng nhận xét của bạn là hướng vào người sai.
HelloWorldPeace

13

Bạn chỉ cần quyết định xem bạn thực sự muốn bao nhiêu chữ số thập phân - không thể có bánh và ăn nó :-)

Lỗi số tích lũy với mỗi thao tác tiếp theo và nếu bạn không cắt bỏ sớm thì nó sẽ phát triển. Các thư viện số hiển thị kết quả trông đơn giản chỉ cần cắt bỏ 2 chữ số cuối cùng ở mỗi bước, các bộ đồng xử lý số cũng có chiều dài "bình thường" và "đầy đủ" vì cùng một lý do. Cuf-off có giá rẻ cho bộ xử lý nhưng rất đắt cho bạn trong tập lệnh (nhân và chia và sử dụng POV (...)). Lib toán học tốt sẽ cung cấp sàn (x, n) để thực hiện cắt giảm cho bạn.

Vì vậy, ít nhất bạn nên tạo var / hằng toàn cầu với POV (10, n) - có nghĩa là bạn đã quyết định độ chính xác bạn cần :-) Sau đó, làm:

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

Bạn cũng có thể tiếp tục làm toán và chỉ bị cắt ở cuối - giả sử rằng bạn chỉ hiển thị và không làm if-s với kết quả. Nếu bạn có thể làm điều đó, thì .toFixed (...) có thể hiệu quả hơn.

Nếu bạn đang thực hiện if-s / so sánh và không muốn cắt thì bạn cũng cần một hằng số nhỏ, thường được gọi là eps, cao hơn một chữ số thập phân so với sai số dự kiến ​​tối đa. Giả sử điểm cắt của bạn là hai số thập phân cuối cùng - sau đó eps của bạn có 1 ở vị trí thứ 3 so với vị trí cuối cùng (ít quan trọng thứ 3) và bạn có thể sử dụng nó để so sánh xem kết quả có nằm trong phạm vi eps dự kiến ​​hay không (0,02 -eps <0,1 * 0,2 <0,02 + eps).


Bạn cũng có thể thêm 0,5 để làm tròn số của một người nghèo: Math.floor (x * PREC_LIM + 0,5) / PREC_LIM
cmroanirgo

Lưu ý mặc dù, ví dụ như rằng Math.floor(-2.1)-3. Vì vậy, có lẽ sử dụng ví dụMath[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
MikeM

Tại sao floorthay vì round?
Quinn Comendant

12

Bạn có thể sử dụng parseFloat()toFixed()nếu bạn muốn bỏ qua vấn đề này cho một thao tác nhỏ:

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

Hàm round () tại phpjs.org hoạt động độc đáo: http://phpjs.org/fifts/round

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

2
@jrg Theo quy ước, các số kết thúc bằng "5" được làm tròn đến số chẵn gần nhất (vì luôn luôn làm tròn lên hoặc xuống sẽ giới thiệu sai lệch cho kết quả của bạn). Do đó, 4.725 làm tròn đến hai chữ số thập phân thực sự phải là 4,72.
Đánh dấu A. Durham

9

0,6 * 3 thật tuyệt vời!)) Đối với tôi điều này hoạt động tốt:

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

Rất rất đơn giản))


Điều này sẽ làm việc mặc dù với một cái gì đó như thế 8.22e-8 * 1.3nào?
Paul Carlton

0,6 x 3 = 1,8, mã bạn cho kết quả là 2 ... nên không tốt.
Zyo

@Zyo Nó trả về 1,8 trong trường hợp này. Làm thế nào bạn chạy nó?
Drenai

Hấp dẫn. Bạn có thể trao đổi các toán tử nhân và chia trong này và nó cũng hoạt động.
Andrew

9

Lưu ý rằng đối với mục đích sử dụng chung, hành vi này có thể được chấp nhận.
Vấn đề phát sinh khi so sánh các giá trị dấu phẩy động đó để xác định một hành động thích hợp.
Với sự ra đời của ES6, một hằng số mới Number.EPSILONđược xác định để xác định tỷ lệ lỗi chấp nhận được:
Vì vậy, thay vì thực hiện so sánh như thế này

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

bạn có thể định nghĩa một chức năng so sánh tùy chỉnh, như thế này:

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

Nguồn: http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon


Trong trường hợp của tôi Number.EPSILON quá nhỏ, dẫn đến ví dụ0.9 !== 0.8999999761581421
Tom

8

Kết quả bạn nhận được là chính xác và khá nhất quán trong các triển khai dấu phẩy động trong các ngôn ngữ, bộ xử lý và hệ điều hành khác nhau - điều duy nhất thay đổi là mức độ không chính xác khi số float thực sự là gấp đôi (hoặc cao hơn).

0,1 trong các dấu phẩy động nhị phân giống như 1/3 trong số thập phân (tức là 0,333333333333 ... mãi mãi), không có cách nào chính xác để xử lý nó.

Nếu bạn đang xử lý các số float luôn mong đợi các lỗi làm tròn nhỏ, do đó, bạn cũng sẽ luôn phải làm tròn kết quả được hiển thị thành một cái gì đó hợp lý. Đổi lại, bạn nhận được số học rất nhanh và mạnh mẽ bởi vì tất cả các tính toán nằm trong hệ nhị phân nguyên gốc của bộ xử lý.

Hầu hết thời gian, giải pháp là không chuyển sang số học điểm cố định, chủ yếu là vì nó chậm hơn nhiều và 99% thời gian bạn không cần độ chính xác. Nếu bạn đang xử lý các công cụ cần mức độ chính xác đó (ví dụ: giao dịch tài chính) thì Javascript có thể không phải là công cụ tốt nhất để sử dụng (vì bạn muốn thực thi các loại điểm cố định, ngôn ngữ tĩnh có thể tốt hơn ).

Bạn đang tìm kiếm giải pháp tao nhã thì tôi e rằng đây là: phao nhanh nhưng có lỗi làm tròn nhỏ - luôn làm tròn thành thứ gì đó hợp lý khi hiển thị kết quả của chúng.


8

Để tránh điều này, bạn nên làm việc với các giá trị nguyên thay vì các dấu phẩy động. Vì vậy, khi bạn muốn có 2 vị trí chính xác làm việc với các giá trị * 100, đối với 3 vị trí sử dụng 1000. Khi hiển thị, bạn sử dụng một bộ định dạng để đặt vào dấu phân cách.

Nhiều hệ thống bỏ qua làm việc với số thập phân theo cách này. Đó là lý do tại sao nhiều hệ thống hoạt động với xu (dưới dạng số nguyên) thay vì đô la / euro (dưới dạng dấu phẩy động).


7

Vấn đề

Điểm nổi không thể lưu trữ chính xác tất cả các giá trị thập phân. Vì vậy, khi sử dụng các định dạng dấu phẩy động sẽ luôn có lỗi làm tròn trên các giá trị đầu vào. Các lỗi trên đầu vào của khóa học kết quả về lỗi trên đầu ra. Trong trường hợp hàm hoặc toán tử rời rạc, có thể có sự khác biệt lớn về đầu ra xung quanh điểm mà hàm hoặc toán tử rời rạc.

Đầu vào và đầu ra cho các giá trị dấu phẩy động

Vì vậy, khi sử dụng các biến dấu phẩy động, bạn nên luôn luôn nhận thức được điều này. Và bất kỳ đầu ra nào bạn muốn từ một phép tính với các dấu phẩy động phải luôn được định dạng / điều hòa trước khi hiển thị với ý nghĩ này.
Khi chỉ sử dụng các hàm và toán tử liên tục, làm tròn đến độ chính xác mong muốn thường sẽ làm (không cắt bớt). Các tính năng định dạng tiêu chuẩn được sử dụng để chuyển đổi float thành chuỗi thường sẽ làm điều này cho bạn.
Bởi vì làm tròn thêm một lỗi có thể làm cho tổng sai số lớn hơn một nửa độ chính xác mong muốn, đầu ra phải được sửa dựa trên độ chính xác dự kiến ​​của đầu vào và độ chính xác đầu ra mong muốn. Bạn nên

  • Đầu vào làm tròn với độ chính xác dự kiến ​​hoặc đảm bảo không có giá trị nào có thể được nhập với độ chính xác cao hơn.
  • Thêm một giá trị nhỏ vào các đầu ra trước khi làm tròn / định dạng chúng nhỏ hơn hoặc bằng 1/4 độ chính xác mong muốn và lớn hơn sai số tối đa dự kiến ​​gây ra bởi lỗi làm tròn trên đầu vào và trong khi tính toán. Nếu điều đó là không thể, sự kết hợp độ chính xác của kiểu dữ liệu được sử dụng sẽ không đủ để cung cấp độ chính xác đầu ra mong muốn cho tính toán của bạn.

Hai điều này thường không được thực hiện và trong hầu hết các trường hợp, sự khác biệt gây ra bởi việc không thực hiện chúng quá nhỏ không quan trọng đối với hầu hết người dùng, nhưng tôi đã có một dự án mà người dùng không chấp nhận đầu ra mà không cần chỉnh sửa.

Các hàm hoặc toán tử rời rạc (như modula)

Khi các toán tử hoặc hàm rời rạc có liên quan, có thể cần phải hiệu chỉnh thêm để đảm bảo đầu ra như mong đợi. Làm tròn và thêm các hiệu chỉnh nhỏ trước khi làm tròn không thể giải quyết vấn đề.
Một kiểm tra / hiệu chỉnh đặc biệt về kết quả tính toán trung gian, ngay sau khi áp dụng hàm hoặc toán tử rời rạc có thể được yêu cầu. Đối với một trường hợp cụ thể (toán tử modula), hãy xem câu trả lời của tôi về câu hỏi: Tại sao toán tử mô đun trả về số phân số trong javascript?

Tốt hơn nên tránh có vấn đề

Việc tránh các vấn đề này thường hiệu quả hơn bằng cách sử dụng các loại dữ liệu (định dạng số nguyên hoặc điểm cố định) cho các phép tính như thế này có thể lưu trữ đầu vào dự kiến ​​mà không làm tròn lỗi. Một ví dụ về điều đó là bạn không bao giờ nên sử dụng các giá trị dấu phẩy động để tính toán tài chính.


4

Có một cái nhìn vào số học điểm cố định . Nó có thể sẽ giải quyết vấn đề của bạn, nếu phạm vi số bạn muốn hoạt động là nhỏ (ví dụ: tiền tệ). Tôi sẽ làm tròn nó thành một vài giá trị thập phân, đó là giải pháp đơn giản nhất.


5
Vấn đề không phải là dấu phẩy động so với điểm cố định, vấn đề là nhị phân so với thập phân.
Michael Borgwardt

4

Hãy thử thư viện số học chiliadic của tôi, mà bạn có thể thấy ở đây . Nếu bạn muốn có một phiên bản mới hơn, tôi có thể lấy cho bạn một phiên bản.


4

Bạn không thể biểu diễn hầu hết các phân số thập phân chính xác bằng các loại dấu phẩy động nhị phân (đó là những gì ECMAScript sử dụng để biểu thị các giá trị dấu phẩy động). Vì vậy, không có một giải pháp tao nhã trừ khi bạn sử dụng các loại số học chính xác tùy ý hoặc một loại dấu phẩy động dựa trên số thập phân. Ví dụ: ứng dụng Máy tính đi kèm với Windows hiện sử dụng số học chính xác tùy ý để giải quyết vấn đề này .


4

nhập mô tả hình ảnh ở đây

    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

Bạn đã đúng, lý do cho điều đó là độ chính xác hạn chế của số dấu phẩy động. Lưu trữ số hữu tỷ của bạn dưới dạng chia hai số nguyên và trong hầu hết các tình huống, bạn sẽ có thể lưu trữ số mà không bị mất độ chính xác. Khi nói đến in, bạn có thể muốn hiển thị kết quả dưới dạng phân số. Với đại diện tôi đề xuất, nó trở nên tầm thường.

Tất nhiên điều đó sẽ không giúp được nhiều với những con số vô lý. Nhưng bạn có thể muốn tối ưu hóa các tính toán của mình theo cách chúng sẽ gây ra ít vấn đề nhất (ví dụ: phát hiện các tình huống như thế nào sqrt(3)^2).


Bạn đã đúng, lý do cho điều đó là độ chính xác hạn chế của các số dấu phẩy động - <pedant>thực ra, OP đã đặt nó xuống để không chính xác các hoạt động của dấu phẩy động, đó là sai</pedant>
đáng ghét

3

Tôi gặp vấn đề làm tròn lỗi khó chịu với mod 3. Đôi khi, khi tôi nhận được 0, tôi sẽ nhận được .000 ... 01. Điều đó đủ dễ để xử lý, chỉ cần kiểm tra <= .01. Nhưng sau đó đôi khi tôi sẽ nhận được 2.99999999999998. NGOÀI RA!

BigNumbers đã giải quyết vấn đề, nhưng đưa ra một vấn đề khác, hơi mỉa mai. Khi thử tải 8,5 vào BigNumbers, tôi được thông báo rằng nó thực sự là 8.4999 và có hơn 15 chữ số có nghĩa. Điều này có nghĩa là BigNumbers không thể chấp nhận nó (tôi tin rằng tôi đã đề cập vấn đề này có phần mỉa mai).

Giải pháp đơn giản cho vấn đề mỉa mai:

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

Sử dụng Số (1.234443) .toFixed (2); nó sẽ in 1,23

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

2

binary.js , big.js hoặc bignumber.js có thể được sử dụng để tránh các vấn đề thao tác dấu phẩy động trong 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: tối giản; dễ sử dụng; độ chính xác quy định ở vị trí thập phân; độ chính xác chỉ áp dụng cho phân chia.

bignumber.js: cơ sở 2-64; Tùy chọn cấu hình; NaN; Vô cực; độ chính xác quy định ở vị trí thập phân; độ chính xác chỉ áp dụng cho phân chia; tiền tố cơ sở.

binary.js: cơ sở 2-64; Tùy chọn cấu hình; NaN; Vô cực; quyền hạn không nguyên, exp, ln, log; độ chính xác quy định bằng chữ số có nghĩa; độ chính xác luôn được áp dụng; Số ngẫu nhiên.

liên kết để so sánh chi tiết


2

Thanh lịch, Dự đoán và Tái sử dụng

Hãy giải quyết vấn đề theo cách có thể tái sử dụng một cách tao nhã. Bảy dòng sau đây sẽ cho phép bạn truy cập độ chính xác của dấu phẩy động mà bạn mong muốn trên bất kỳ số nào chỉ bằng cách nối thêm .decimalvào cuối số, công thức hoặc Mathhàm tích hợp.

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

Chúc mừng!


2
Nếu bạn chọn downvote, ít nhất là cung cấp một lý do.
Bernesto

1

Sử dụng

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

4
Hmm ... nhưng lưu ý, điều này luôn làm tròn đến 2 số thập phân. Tất nhiên đó sẽ là một lựa chọn, nhưng về phép tính 0,55 * 0,55 (vì tôi không biết trước các con số chính xác. Điều đó sẽ cho 0,3 thay vì 0,3025. Tất nhiên sau đó tôi có thể sử dụng Math.round(x*Math.pow(10,4))/Math.pow(10,4);. Làm tròn luôn là một tùy chọn, nhưng tôi chỉ muốn biết liệu có giải pháp nào tốt hơn không
Juri



1

Điều này làm việc cho tôi:

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
thật không may, round_up (4.15,2) => 4.16.
JRG

1

Đầu ra sử dụng chức năng sau:

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();

Hãy chú ý đến đầu ra toFixedCurrency(x).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.