javascript
  • floating-point
  • floating-accuracy
  • ieee-754
  • 2009-09-04 4291 views 38 likes 
    38

    Xem mã này:số lớn sai lầm làm tròn trong Javascript

    <html> 
    <head> 
    
    <script src="http://www.json.org/json2.js" type="text/javascript"></script> 
    
    <script type="text/javascript"> 
    
        var jsonString = '{"id":714341252076979033,"type":"FUZZY"}'; 
        var jsonParsed = JSON.parse(jsonString); 
        console.log(jsonString, jsonParsed); 
    
    
    </script> 
    </head> 
    <body> 
    </body> 
    </html> 
    

    Khi tôi nhìn thấy giao diện điều khiển của tôi trong Firefox 3.5, giá trị của jsonParsed là:

    Object id=714341252076979100 type=FUZZY 
    

    tức là số được làm tròn. Đã thử các giá trị khác nhau, cùng một kết quả (số được làm tròn).

    Tôi cũng không nhận được các quy tắc làm tròn của nó. 714341252076979136 được làm tròn thành 714341252076979200, trong khi 714341252076979135 được làm tròn thành 714341252076979100.

    EDIT: xem bình luận đầu tiên bên dưới. Rõ ràng đây không phải là về JSON, nhưng một cái gì đó về xử lý số Javascript. Nhưng câu hỏi vẫn còn:

    Tại sao điều này lại xảy ra?

    +0

    Cảm ơn tất cả các câu trả lời hữu ích nhanh chóng, tôi ước tôi có thể đánh dấu tất cả 3 là câu trả lời chính thức. – Jaanus

    Trả lời

    47

    Những gì bạn thấy ở đây thực sự là hiệu ứng của hai vòng. Các số trong ECMAScript được biểu diễn bằng dấu phẩy động kép chính xác nội bộ. Khi id được đặt thành 714341252076979033 (0x9e9d9958274c359 trong hex), nó thực sự được gán giá trị chính xác kép có thể biểu diễn gần nhất, là 714341252076979072 (0x9e9d9958274c380). Khi bạn in ra giá trị, nó được làm tròn đến 15 chữ số thập phân có ý nghĩa, cho số 14341252076979100.

    7

    Không phải do trình phân tích cú pháp json này gây ra. Chỉ cần thử nhập 714341252076979033 vào bảng điều khiển của bọ rùa. Bạn sẽ thấy cùng 714341252076979100.

    Xem bài viết trên blog này để biết chi tiết: http://www.exploringbinary.com/print-precision-of-floating-point-integers-varies-too

    +6

    Cảm ơn bạn đã liên kết đến bài viết của tôi, nhưng nó chỉ giải thích một nửa vấn đề - IN của giá trị được làm tròn bên trong. Ngay cả khi javascript cho phép bạn in toàn bộ nội dung, nó vẫn sẽ sai - nó sẽ là giá trị chính xác kép có thể biểu diễn gần nhất, như được giải thích bởi những người khác bên dưới. –

    40

    Bạn đang tràn năng lực của loại số JavaScript, xem §8.5 of the spec để biết chi tiết. Những ID đó sẽ cần phải là chuỗi.

    Điểm nổi chính xác kép IEEE-754 (loại sử dụng JavaScript số) không thể đại diện chính xác tất cả số (tất nhiên). Nổi tiếng, 0.1 + 0.2 == 0.3 là sai. Điều đó có thể ảnh hưởng đến số nguyên giống như nó ảnh hưởng đến các số phân số; nó bắt đầu khi bạn nhận được trên 9,007,199,254,740,991 (Number.MAX_SAFE_INTEGER).

    Ngoài Number.MAX_SAFE_INTEGER + 1 (9007199254740992), định dạng điểm nổi IEEE-754 không thể đại diện cho mọi số nguyên liên tiếp nữa. 9007199254740991 + 19007199254740992, nhưng 9007199254740992 + 1cũng9007199254740992 vì không thể đại diện cho 9007199254740993 ở định dạng. Tiếp theo có thể là 9007199254740994. Sau đó, 9007199254740995 không thể, nhưng 9007199254740996 có thể.

    Lý do là chúng tôi đã hết bit, vì vậy chúng tôi không còn bit 1 giây nữa; bit thứ tự thấp nhất hiện nay đại diện cho bội số của 2. Cuối cùng, nếu chúng ta tiếp tục, chúng ta mất bit đó và chỉ làm việc trong bội số của 4. Và cứ thế.

    Giá trị của bạn là cũng cao hơn ngưỡng đó và do đó chúng được làm tròn thành giá trị thể hiện gần nhất.


    Nếu bạn tò mò về các bit, đây là những gì sẽ xảy ra: Một IEEE-754 nhị phân đúp chính xác số dấu chấm động có một chút dấu hiệu, 11 bit của số mũ (trong đó xác định quy mô tổng thể về số , như là một sức mạnh của 2 [bởi vì đây là một định dạng nhị phân]), và 52 bit của significand (nhưng định dạng rất thông minh nó được 53 bit chính xác trong số 52 bit đó). Cách số mũ được sử dụng phức tạp (described here), nhưng trong rất thuật ngữ mơ hồ, nếu chúng ta thêm một vào số mũ, giá trị của significand được tăng gấp đôi, vì số mũ được sử dụng cho lũy thừa 2 (một lần nữa, hãy báo trước ở đó, nó không trực tiếp, có sự thông minh trong đó).

    Vì vậy, chúng ta hãy nhìn vào giá trị 9007199254740991 (aka, Number.MAX_SAFE_INTEGER):

     
       +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit 
      / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent 
     //  | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand 
    //   |/            | 
    0 10000110011 1111111111111111111111111111111111111111111111111111 
                    = 9007199254740991 (Number.MAX_SAFE_INTEGER) 
    

    Đó giá trị số mũ, 10000110011, có nghĩa là mỗi lần chúng tôi thêm một đến significand, số đại diện đi lên bằng 1 (các toàn bộ số 1, chúng tôi đã mất khả năng biểu diễn số phân số sớm hơn nhiều).

    Nhưng bây giờ điều đó có nghĩa là đầy. Để vượt qua con số đó, chúng ta phải tăng số mũ, có nghĩa là nếu chúng ta thêm số mũ vào giá trị và giá trị của số được biểu thị tăng lên 2, không phải 1 (vì số mũ được áp dụng cho 2, cơ sở của số này số dấu chấm động nhị phân):

     
       +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit 
      / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent 
     //  | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand 
    //   |/            | 
    0 10000110100 0000000000000000000000000000000000000000000000000000 
                    = 9007199254740992 (Number.MAX_SAFE_INTEGER + 1) 
    

    Vâng, đó là okay, vì 9007199254740991 + 19007199254740992 anyway. Nhưng! Chúng tôi không thể đại diện cho 9007199254740993. Chúng tôi đã hết bit. Nếu chúng tôi chỉ thêm 1 vào mức độ có nghĩa là, nó thêm 2 vào giá trị:

     
       +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− sign bit 
      / +−−−−−−−+−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−− exponent 
     //  | +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+− significand 
    //   |/            | 
    0 10000110100 0000000000000000000000000000000000000000000000000001 
                    = 9007199254740994 (Number.MAX_SAFE_INTEGER + 3) 
    

    Định dạng không thể đại diện cho số lẻ nữa khi chúng tôi tăng giá trị, số mũ quá lớn.

    Cuối cùng, chúng tôi chạy ra khỏi significand bit một lần nữa và phải tăng số mũ, vì vậy chúng tôi kết thúc chỉ có thể đại diện cho bội số của 4. Sau đó bội số của 8. Sau đó bội số của 16. Và như vậy.

    +4

    Tôi thích câu trả lời này vì nó thực sự cho bạn biết cách giải quyết vấn đề. – jsh

    2

    Vấn đề là số của bạn yêu cầu độ chính xác cao hơn JavaScript.

    Bạn có thể gửi số dưới dạng chuỗi không? Tách trong hai phần?

    4

    JavaScript sử dụng chính xác đôi nổi giá trị điểm, tức là tổng độ chính xác của 53 bit, nhưng bạn cần

    ceil(lb 714341252076979033) = 60 
    

    bit chính xác đại diện cho giá trị.

    Số chính xác gần nhất là 714341252076979072 (viết số gốc dưới dạng nhị phân, thay thế 7 chữ số cuối cùng bằng 0 và tròn lên vì chữ số được thay thế cao nhất là 1).

    Bạn sẽ nhận được 714341252076979100 thay vì số này bởi vì ToString() như được mô tả bởi ECMA-262, §9.8.1 hoạt động với quyền hạn mười và chính xác 53 bit tất cả các số này bằng nhau.

    1

    JavaScript chỉ có thể xử lý toàn bộ con số chính xác lên tới khoảng 9000 triệu triệu (đó là 9 với 15 số không). Cao hơn thế và bạn có rác. Làm việc xung quanh điều này bằng cách sử dụng chuỗi để giữ các số. Nếu bạn cần làm toán với những con số này, hãy viết các hàm của riêng bạn hoặc xem liệu bạn có thể tìm thấy một thư viện cho chúng hay không: Tôi đề nghị trước đây vì tôi không thích các thư viện mà tôi đã thấy. Để giúp bạn bắt đầu, hãy xem hai chức năng của tôi tại another answer.

    Các vấn đề liên quan