Tại sao số học dấu phẩy động không chính xác trong JavaScript?

Không giống như nhiều ngôn ngữ lập trình khác, JavaScript không xác định các loại số khác nhau, như số nguyên, ngắn, dài, dấu phẩy động, v.v.

Số JavaScript luôn được lưu trữ dưới dạng số dấu phẩy động có độ chính xác kép, tuân theo tiêu chuẩn quốc tế IEEE 754

Định dạng này lưu trữ các số trong 64 bit, trong đó số (phân số) được lưu trữ trong các bit từ 0 đến 51, số mũ trong các bit từ 52 đến 62 và dấu ở bit 63

Giá trị (còn gọi là Phân số/Phần định trị)Số mũDấu52 bit (0 - 51) 11 bit (52 - 62)1 bit (63)

Độ chính xác số nguyên

Số nguyên (số không có dấu chấm hoặc ký hiệu số mũ) chính xác đến 15 chữ số

Thí dụ

đặt x = 999999999999999;
cho y = 9999999999999999;

Tự mình thử »

Số thập phân lớn nhất là 17

độ chính xác nổi

Để giải bài toán trên giúp nhân chia

đặt x = (0. 2 * 10 + 0. 1*10)/10;

Tự mình thử »



Thêm số và chuỗi

CẢNH BÁO

JavaScript sử dụng toán tử + cho cả phép cộng và phép nối

Số được thêm vào. Chuỗi được nối

Nếu bạn cộng hai số, kết quả sẽ là một số

Nếu bạn thêm hai chuỗi, kết quả sẽ là nối chuỗi

Nếu bạn thêm một số và một chuỗi, kết quả sẽ là nối chuỗi

Nếu bạn thêm một chuỗi và một số, kết quả sẽ là nối chuỗi

Một sai lầm phổ biến là mong đợi kết quả này là 30

Một sai lầm phổ biến là mong đợi kết quả này là 102030

Trình thông dịch JavaScript hoạt động từ trái sang phải

10 + 20 đầu tiên được thêm vào vì x và y đều là số

Khi đó 30 + "30" được nối với nhau vì z là một chuỗi


Chuỗi số

Chuỗi JavaScript có thể có nội dung số

cho x = 100;

cho y = "100";

JavaScript sẽ cố gắng chuyển đổi chuỗi thành số trong tất cả các hoạt động số

Điều này sẽ làm việc

Điều này cũng sẽ làm việc

Và điều này sẽ làm việc

Nhưng điều này sẽ không làm việc

Trong ví dụ trước, JavaScript sử dụng toán tử + để nối các chuỗi


NaN - Không phải là số

NaN là một từ dành riêng cho JavaScript chỉ ra rằng một số không phải là số hợp lệ

Cố gắng làm phép tính với một chuỗi không phải là số sẽ dẫn đến kết quả là NaN (Không phải là số)

Tuy nhiên, nếu chuỗi chứa giá trị số, kết quả sẽ là một số

Bạn có thể sử dụng hàm JavaScript toàn cục isNaN() để tìm hiểu xem giá trị có phải là số không

Coi chừng NaN. Nếu bạn sử dụng NaN trong một phép toán, kết quả cũng sẽ là NaN

Hoặc kết quả có thể là một phép nối như NaN5

NaN là một số. typeof NaN trả lại number


vô cực

________ 61 _______ (hoặc _______ 52 _______0) là giá trị mà JavaScript sẽ trả về nếu bạn tính toán một số nằm ngoài số lớn nhất có thể

Các số phân số có thể khó đối với máy tính - và điều đó có thể dẫn đến các kết quả và lỗi không mong muốn khi làm toán với chúng. Do đó hiểu số học dấu phẩy động là chìa khóa

Tạo bởi Maximilian Schwarzmüller

Ngày 17 tháng 11 năm 2021

Vấn đề là gì?

Đây là một ví dụ từ JavaScript - mặc dù về cơ bản có thể tìm thấy các ví dụ tương tự cho tất cả các ngôn ngữ lập trình

const result = 0.1 + 0.2;
console.log(result); // prints 0.30000000000000004

Đó là cái gì?

Máy tính gặp vấn đề với một số phân số - và bài viết này khám phá lý do tại sao lại như vậy và những điều bạn nên ghi nhớ

Máy tính sử dụng hệ thống số nhị phân trong nội bộ

Gốc rễ của vấn đề là các máy tính đang sử dụng hệ thống số nhị phân dưới vỏ bọc. Hiểu cách thức hoạt động của nó và cách bạn có thể chuyển đổi số từ hệ thống số thập phân sang hệ thống nhị phân sẽ giúp ích rất nhiều. Tôi có một bài viết khác về điều đó - chắc chắn hãy cân nhắc xem qua bài viết đó trước

Nói tóm lại, khi làm việc với hệ thống số nhị phân, bạn chỉ có thể làm việc với hai số. 0

0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
0. Tất cả các số khác phải được biểu thị bằng hai số này

Nó thực sự giống nhau trong hệ thống thập phân - ở đó bạn có các số từ 0 đến

0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
2 để làm việc và tất cả các số khác phải được biểu thị bằng các số này (e. g.
0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
3 là kết hợp của
0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
0 và
0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
2). Nhưng chúng ta đã quen với hệ thống thập phân hơn, đó là lý do tại sao chúng ta thường không gặp vấn đề gì với hệ thống đó

Nhưng quay lại hệ thống số nhị phân. Tại sao nó gây ra lỗi lạ được đề cập ở trên?

Một số con số không thể được thể hiện chính xác

Trong ví dụ trên (0.1 + 0.2), vấn đề là cả

0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
7 và
0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
8 đều không thể được máy tính xử lý và lưu trữ chính xác. Bởi vì mặc dù chúng dễ xử lý và biểu thị trong hệ thập phân, nhưng đó không phải là trường hợp của hệ nhị phân

Để hiểu vấn đề, chúng ta hãy xem xét một số không thể biểu thị và lưu trữ chính xác trong hệ thống số thập phân.

0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
9

0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
9 là một số hợp lệ nhưng không phải là số mà chúng ta có thể diễn đạt chính xác. Thay vào đó, nó là
0.1 = 0 * 2⁰ + 0 * 2⁻¹ + 0 * 2⁻² + 0 * 2⁻³ + 1 * 2⁻⁴ + 1 * 2⁻⁵ + 0 * 2⁻⁶ + 0 * 2⁻⁷ + 1 * 2⁻⁸ + ...
1 và chúng ta có thể tiếp tục thêm
0.1 = 0 * 2⁰ + 0 * 2⁻¹ + 0 * 2⁻² + 0 * 2⁻³ + 1 * 2⁻⁴ + 1 * 2⁻⁵ + 0 * 2⁻⁶ + 0 * 2⁻⁷ + 1 * 2⁻⁸ + ...
2 vào cuối cho đến hết thời gian

Bây giờ hãy xem xét trường hợp mà bạn muốn thêm

0.1 = 0 * 2⁰ + 0 * 2⁻¹ + 0 * 2⁻² + 0 * 2⁻³ + 1 * 2⁻⁴ + 1 * 2⁻⁵ + 0 * 2⁻⁶ + 0 * 2⁻⁷ + 1 * 2⁻⁸ + ...
3. Là một con người, chúng ta biết rằng điều này tương đương với
0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
0

Nhưng nếu chúng ta làm phép toán với

0.1 = 0 * 2⁰ + 0 * 2⁻¹ + 0 * 2⁻² + 0 * 2⁻³ + 1 * 2⁻⁴ + 1 * 2⁻⁵ + 0 * 2⁻⁶ + 0 * 2⁻⁷ + 1 * 2⁻⁸ + ...
5, chúng ta có thể đi đến kết luận rằng kết quả thực sự phải là
0.1 = 0 * 2⁰ + 0 * 2⁻¹ + 0 * 2⁻² + 0 * 2⁻³ + 1 * 2⁻⁴ + 1 * 2⁻⁵ + 0 * 2⁻⁶ + 0 * 2⁻⁷ + 1 * 2⁻⁸ + ...
6

Tất nhiên, là một con người, chúng ta biết rõ hơn. Chúng tôi biết khái niệm về phân số và chúng tôi biết rằng

0.1 = 0 * 2⁰ + 0 * 2⁻¹ + 0 * 2⁻² + 0 * 2⁻³ + 1 * 2⁻⁴ + 1 * 2⁻⁵ + 0 * 2⁻⁶ + 0 * 2⁻⁷ + 1 * 2⁻⁸ + ...
7

Nhưng máy tính không biết điều đó - và đó là vấn đề

Hãy chuyển sang hệ thống nhị phân - bởi vì đó là hệ thống số mà máy tính hoạt động bên trong

0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
7 là
0.1 = 0 * 2⁰ + 0 * 2⁻¹ + 0 * 2⁻² + 0 * 2⁻³ + 1 * 2⁻⁴ + 1 * 2⁻⁵ + 0 * 2⁻⁶ + 0 * 2⁻⁷ + 1 * 2⁻⁸ + ...
9 trong hệ thập phân (và điều đó tất nhiên không có vấn đề gì - chúng ta có thể diễn đạt chính xác con số này)

Nhưng hệ thống nhị phân tương đương với

0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
7 thực sự là
0.1 = 0.00011001...
1, trong đó mẫu
0.1 = 0.00011001...
2 thực sự cũng lặp lại vô hạn

Trong hệ thập phân, các số

0.1 = 0.00011001...
3 được thể hiện bằng cách chia cho
0.1 = 0.00011001...
4 (
0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
8 là
0.1 = 0.00011001...
6,
0.1 = 0.00011001...
7 là
0.1 = 0.00011001...
8, v.v. )

Trong hệ thống nhị phân, nó giống nhau nhưng phép chia phải được thực hiện với

0.1 = 0.00011001...
9 thay vì
0.1 = 0.00011001...
4. Do đó,
0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
7 thực sự được biểu diễn dưới dạng tổng của nhiều số phân số, trong đó mỗi phân số chia hết cho
0.1 = 0.00011001...
9 hoặc
const result == 0.1 + 0.2;
console.log(result === 0.3); // false
3

0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...

Vì chúng ta chỉ có thể chia cho

0.1 = 0.00011001...
9 ở lũy thừa của
const result == 0.1 + 0.2;
console.log(result === 0.3); // false
5, nên không có cách nào để đánh chính xác
0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
7. Chúng ta chỉ có thể ngày càng gần hơn. Cũng như chúng ta có thể ngày càng tiến gần hơn đến kết quả của
0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
9 trong hệ thập phân

Dãy phân số trên cũng có thể viết lại như thế này

0.1 = 0 * 2⁰ + 0 * 2⁻¹ + 0 * 2⁻² + 0 * 2⁻³ + 1 * 2⁻⁴ + 1 * 2⁻⁵ + 0 * 2⁻⁶ + 0 * 2⁻⁷ + 1 * 2⁻⁸ + ...

Như bạn đã học trong bài viết khác, đây là cách bạn có thể chuyển đổi số thập phân sang số nhị phân. Bây giờ bạn có thể lấy các số nhân (các số

0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
0 và số 0) và nối chúng với số nhị phân

0.1 = 0.00011001...

Vì vậy, các số có thể được biểu thị chính xác trong một hệ thống số không nhất thiết cũng có thể được biểu thị chính xác trong các hệ thống số khác

Máy tính giải quyết vấn đề đó như thế nào?

Máy tính và ngôn ngữ lập trình đang chuyển đổi số mọi lúc. Và để xử lý và lưu trữ các giá trị như

0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
7 (số hệ thập phân) một cách chính xác, chúng sẽ làm tròn các giá trị nhị phân tại một số điểm

Vì vậy, số nhị phân

0.1 = 0.00011001...
1 được làm tròn thành một giá trị như
const result = 0.1 + 0.2;
const formattedResult = result.toFixed(2); // toFixed(x) returns a string with x numbers after the decimal point
console.log(formattedResult); // prints 0.30
2 (đây chỉ là một ví dụ. ). Số chữ số thập phân được thể hiện chính xác (i. e. không được làm tròn) phụ thuộc vào độ chính xác mà số sẽ được lưu trữ và trên hệ thống cơ bản. Trong hệ thống 32 bit, 23 chữ số sau dấu thập phân có thể được lưu trữ

Độ chính xác xác định có bao nhiêu bit sẽ được chiếm trong bộ nhớ để lưu số. Càng nhiều bit được cung cấp, càng có nhiều chữ số sau dấu thập phân có thể được lưu trữ trước khi làm tròn xảy ra

Bạn có thể đã nghe các thuật ngữ như độ chính xác đơn hoặc độ chính xác kép khi định nghĩa các biến trong một số ngôn ngữ lập trình nhất định. Đó là ý nghĩa của chúng - có bao nhiêu số (sau dấu thập phân) được lưu trữ chính xác trước khi làm tròn số

Điều đáng chú ý là các số không thực sự được lưu dưới dạng

const result = 0.1 + 0.2;
const formattedResult = result.toFixed(2); // toFixed(x) returns a string with x numbers after the decimal point
console.log(formattedResult); // prints 0.30
3 trong bộ nhớ. Thay vào đó, về cơ bản chúng được lưu trữ dưới dạng ký hiệu khoa học - bạn có thể tìm hiểu thêm tại đây

Vì vậy, độ chính xác không phải là số chữ số sau dấu thập phân. Nhưng nó vẫn ảnh hưởng đến số lượng chữ số có thể được biểu thị trước khi làm tròn có hiệu lực

Trong các phép tính như 0.1 + 0.2, cả

0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
7 và
0.1 = 0/2⁰ + 0/2¹ + 0/2² + 0/2³ + 1/2⁴ + 1/2⁵ + 1/2⁸ + ...
8 đều không thể được biểu thị chính xác trong hệ thống nhị phân, do đó các lỗi làm tròn có thể ảnh hưởng đến kết quả - đó là lý do tại sao các kết quả như
const result = 0.1 + 0.2;
const formattedResult = result.toFixed(2); // toFixed(x) returns a string with x numbers after the decimal point
console.log(formattedResult); // prints 0.30
7 có thể được suy ra và hiển thị

Cách xử lý các giá trị không chính xác

Có nhiều điều cần xem xét khi làm việc với các số phân số - và các chiến lược khác nhau để giải quyết các vấn đề

Đừng So Sánh Bình Đẳng

Bạn nên cố gắng tránh so sánh bình đẳng (trong-) như thế này

const result == 0.1 + 0.2;
console.log(result === 0.3); // false

Tùy thuộc vào ngôn ngữ mà bạn đang thực hiện việc này, kết quả có thể không phải lúc nào bạn cũng nhận được

const result = 0.1 + 0.2;
const formattedResult = result.toFixed(2); // toFixed(x) returns a string with x numbers after the decimal point
console.log(formattedResult); // prints 0.30
8 - bởi vì, như minh họa ở trên, 0.1 + 0.2 có thể được lưu trữ nội bộ dưới dạng
const result = 0.1 + 0.2;
const formattedResult = result.toFixed(2); // toFixed(x) returns a string with x numbers after the decimal point
console.log(formattedResult); // prints 0.30
7 (không tương đương với chỉ 0.3)

Vì vậy, các lỗi làm tròn có thể và thường sẽ ảnh hưởng đến các phép so sánh như thế này

Thay vì kiểm tra sự bằng nhau (trong-), do đó, nên sử dụng các toán tử như 42 và 43 để so sánh

Giảm số lượng chữ số thập phân khi xuất giá trị

Khi hiển thị kết quả tính toán như 0.1 + 0.2 cho người dùng cuối, bây giờ bạn có thể muốn dựa vào độ chính xác. Ví dụ: khi làm việc với JavaScript, người dùng của bạn có thể phải đối mặt với các kết quả xấu và khó hiểu như

const result = 0.1 + 0.2;
const formattedResult = result.toFixed(2); // toFixed(x) returns a string with x numbers after the decimal point
console.log(formattedResult); // prints 0.30
7 khi truy cập trang web của bạn

Do đó, một chiến lược tốt là "cắt số" theo cách thủ công sau một số chữ số nhất định sau dấu thập phân

Tại sao số dấu phẩy động không chính xác trong JavaScript?

Đó là bởi vì tất cả các số trong JavaScript đều là số dấu phẩy động có độ chính xác gấp đôi theo tiêu chuẩn IEEE 754 . Định dạng này cho phép biểu diễn các số trong khoảng từ 5 x 2^-324 đến 1. 79 * 2^308 bao gồm các phân số.

Tại sao nổi

Các giá trị thập phân có dấu phẩy động thường không có biểu diễn nhị phân chính xác. Đây là tác dụng phụ của cách CPU biểu thị dữ liệu dấu chấm động . Vì lý do này, bạn có thể bị mất một số độ chính xác và một số thao tác dấu phẩy động có thể tạo ra kết quả không mong muốn.

Tại sao là 0. 1 0. 2 không bằng 0. 3 trong JavaScript?

0. 1 + 0. 2. này bằng 0. 3, nhưng ở dạng dấu phẩy động. (0. 1 + 0. 2) == 0. 3 là sai. Điều này là do 0. 1, 0. 2 và 0. 3 không thể được biểu diễn chính xác trong dấu phẩy động cơ số 2 .

Tại sao số dấu phẩy động không thể được lưu trữ chính xác?

Bởi vì thông thường, chúng xấp xỉ các số hữu tỷ không thể biểu diễn hữu hạn trong cơ số 2 (các chữ số lặp lại) và nói chung, chúng xấp xỉ các số thực (có thể là số vô tỷ) mà có thể không thể biểu diễn bằng hữu hạn các chữ số trong bất kỳ cơ số nào