Là toán học dấu phẩy động nhất quán trong C #? Có thể được không?


155

Không, đây không phải là câu hỏi "Tại sao (1 / 3.0) * 3! = 1" khác .

Gần đây tôi đã đọc về các điểm nổi rất nhiều; cụ thể, cách tính toán giống nhau có thể cho kết quả khác nhau trên các kiến ​​trúc hoặc cài đặt tối ưu hóa khác nhau.

Đây là một vấn đề đối với các trò chơi video lưu trữ phát lại, hoặc được kết nối ngang hàng (trái ngược với máy khách-máy khách), phụ thuộc vào tất cả các máy khách tạo ra kết quả chính xác giống nhau mỗi khi chúng chạy chương trình - một sự khác biệt nhỏ trong một Tính toán dấu phẩy động có thể dẫn đến trạng thái trò chơi khác nhau mạnh mẽ trên các máy khác nhau (hoặc thậm chí trên cùng một máy! )

Điều này xảy ra ngay cả trong số các bộ xử lý "theo" IEEE-754 , chủ yếu là do một số bộ xử lý (cụ thể là x86) sử dụng độ chính xác mở rộng gấp đôi . Nghĩa là, họ sử dụng các thanh ghi 80 bit để thực hiện tất cả các phép tính, sau đó cắt ngắn thành 64 hoặc 32 bit, dẫn đến kết quả làm tròn khác với các máy sử dụng các bit 64 hoặc 32 bit để tính toán.

Tôi đã thấy một số giải pháp cho vấn đề này trực tuyến, nhưng tất cả cho C ++, không phải C #:

  • Vô hiệu hóa chế độ chính xác mở rộng gấp đôi (để tất cả các doubletính toán sử dụng IEEE-754 64 bit) bằng cách sử dụng _controlfp_s(Windows), _FPU_SETCW(Linux?) Hoặc fpsetprec(BSD).
  • Luôn chạy cùng một trình biên dịch với cùng các cài đặt tối ưu hóa và yêu cầu tất cả người dùng phải có cùng kiến ​​trúc CPU (không chơi đa nền tảng). Bởi vì "trình biên dịch" của tôi thực sự là JIT, có thể tối ưu hóa khác nhau mỗi khi chương trình được chạy , tôi không nghĩ rằng điều này là có thể.
  • Sử dụng số học điểm cố định, và tránh floatdoublehoàn toàn. decimalsẽ hoạt động cho mục đích này, nhưng sẽ chậm hơn nhiều và không có System.Mathchức năng thư viện nào hỗ trợ nó.

Vì vậy, điều này thậm chí là một vấn đề trong C #? Nếu tôi chỉ có ý định hỗ trợ Windows (không phải Mono) thì sao?

Nếu có, có cách nào để buộc chương trình của tôi chạy ở độ chính xác kép bình thường không?

Nếu không, có thư viện nào giúp duy trì tính toán dấu phẩy động nhất quán không?


Tôi đã thấy câu hỏi này , nhưng mọi câu trả lời đều lặp lại vấn đề mà không có giải pháp, hoặc nói "bỏ qua nó", đó không phải là một lựa chọn. Tôi đã hỏi một câu hỏi tương tự trên gamedev , nhưng (vì khán giả) hầu hết các câu trả lời dường như đều hướng đến C ++.
BlueRaja - Daniel Pflughoeft

1
không phải là một câu trả lời, nhưng tôi chắc chắn rằng bạn trong hầu hết các lĩnh vực bạn có thể thiết kế hệ thống của mình theo cách sao cho tất cả trạng thái chia sẻ là có tính quyết định và không có sự suy giảm hiệu suất đáng kể vì điều đó
Driushkin

1
@Peter bạn có biết bất kỳ mô phỏng điểm nổi nhanh nào cho .net không?
CodeInChaos

1
Java có bị vấn đề này không?
Josh

3
@Josh: Java có strictfptừ khóa, buộc tất cả các tính toán phải được thực hiện theo kích thước đã nêu ( floathoặc double) thay vì kích thước mở rộng. Tuy nhiên, Java vẫn có nhiều vấn đề với hỗ trợ IEE-754. Rất (rất, rất) rất ít ngôn ngữ lập trình hỗ trợ IEE-754 tốt.
porges

Câu trả lời:


52

Tôi biết không có cách nào để làm cho các điểm nổi bình thường xác định trong .net. JITter được phép tạo mã hoạt động khác nhau trên các nền tảng khác nhau (hoặc giữa các phiên bản khác nhau của .net). Vì vậy, sử dụng floats bình thường trong mã .net xác định là không thể.

Cách giải quyết tôi đã xem xét:

  1. Triển khai FixedPoint32 trong C #. Mặc dù điều này không quá khó (tôi đã thực hiện xong một nửa) nhưng phạm vi giá trị rất nhỏ khiến nó khó chịu khi sử dụng. Bạn phải cẩn thận mọi lúc để bạn không bị tràn, cũng không mất quá nhiều độ chính xác. Cuối cùng, tôi thấy điều này không dễ hơn là sử dụng số nguyên trực tiếp.
  2. Triển khai FixedPoint64 trong C #. Tôi thấy điều này khá khó để làm. Đối với một số hoạt động, số nguyên trung gian 128 bit sẽ hữu ích. Nhưng .net không cung cấp loại như vậy.
  3. Thực hiện một dấu phẩy động 32 bit tùy chỉnh. Việc thiếu một BitScanReverse nội tại gây ra một vài phiền toái khi thực hiện điều này. Nhưng hiện tại tôi nghĩ đây là con đường hứa hẹn nhất.
  4. Sử dụng mã gốc cho các hoạt động toán học. Phát sinh chi phí của một cuộc gọi đại biểu cho mọi hoạt động toán học.

Tôi vừa mới bắt đầu triển khai phần mềm toán học dấu phẩy động 32 bit. Nó có thể thực hiện khoảng 70 triệu bổ sung / nhân mỗi giây trên i3 2,66GHz của tôi. https://github.com/CodesInChaos/SoftFloat . Rõ ràng là nó vẫn chưa hoàn thiện và có lỗi.


2
Có một số nguyên có kích thước "không giới hạn" có sẵn BigInteger mặc dù không nhanh bằng int int hoặc dài như vậy nên .NET cung cấp loại như vậy (được tạo cho F # Tôi tin nhưng có thể được sử dụng trong C #)
Rune FS

Một tùy chọn khác là trình bao bọc GNU MP cho .NET . Đây là một trình bao bọc xung quanh Thư viện Chính xác GNU , hỗ trợ các số nguyên chính xác "không xác định", số hữu tỷ (phân số) và số dấu phẩy động.
Cole Johnson

2
Nếu bạn định làm bất cứ điều gì trong số này, decimaltrước tiên bạn cũng có thể thử , vì nó đơn giản hơn nhiều để làm. Chỉ khi nó quá chậm đối với nhiệm vụ trong tay thì các cách tiếp cận khác mới đáng để suy nghĩ.
Roman Starkov

Tôi đã tìm hiểu về một trường hợp đặc biệt trong đó các điểm nổi là xác định. Giải thích tôi nhận được là: Đối với phép nhân / chia, nếu một trong các số FP là lũy thừa của hai số (2 ^ x), đáng kể / mantissa sẽ không thay đổi trong khi tính toán. Chỉ có số mũ sẽ thay đổi (điểm sẽ di chuyển). Vì vậy, làm tròn sẽ không bao giờ xảy ra. Kết quả sẽ mang tính quyết định.
ngoằn ngoèo

Ví dụ: Một số như 2 ^ 32 được biểu diễn dưới dạng (số mũ: 32, mantissa: 1). Nếu chúng ta nhân số này với một số float khác (exp, man), kết quả là (exp + 32, man * 1). Đối với phép chia, kết quả là (expo - 32, man * 1). Nhân số lớp phủ với 1 không làm thay đổi lớp phủ, vì vậy nó không quan trọng là nó có bao nhiêu bit.
ngoằn ngoèo

28

Đặc tả C # (§4.1.6 Các loại dấu phẩy động) đặc biệt cho phép tính toán dấu phẩy động được thực hiện bằng cách sử dụng độ chính xác cao hơn so với kết quả. Vì vậy, không, tôi không nghĩ rằng bạn có thể thực hiện các tính toán đó một cách xác định trực tiếp trong .Net. Những người khác đề xuất cách giải quyết khác nhau, vì vậy bạn có thể thử chúng.


9
Tôi chỉ nhận ra rằng đặc tả C # không thực sự quan trọng nếu một người phân phối các tập hợp đã biên dịch. Nó chỉ quan trọng nếu một người muốn tương thích nguồn. Điều thực sự quan trọng là đặc tả CLR. Nhưng tôi khá chắc chắn rằng bảo đảm của nó cũng yếu như bảo đảm C #.
CodeInChaos

Sẽ không truyền tới doublemỗi lần sau khi một thao tác loại bỏ các bit không mong muốn, mang lại kết quả nhất quán?
IllidanS4 muốn Monica trở lại vào

2
@ IllidanS4 Tôi không nghĩ rằng sẽ đảm bảo kết quả phù hợp.
Svick

14

Trang sau có thể hữu ích trong trường hợp bạn cần tính di động tuyệt đối của các hoạt động đó. Nó thảo luận về phần mềm để thử nghiệm các triển khai của tiêu chuẩn IEEE 754, bao gồm cả phần mềm để mô phỏng các hoạt động của dấu phẩy động. Tuy nhiên, hầu hết các thông tin có thể là cụ thể cho C hoặc C ++.

http://www.math.utah.edu/~beebe/software/ieee/

Một lưu ý về điểm cố định

Số điểm cố định nhị phân cũng có thể hoạt động tốt thay thế cho dấu phẩy động, hiển nhiên từ bốn phép tính số học cơ bản:

  • Phép cộng và phép trừ là chuyện nhỏ. Chúng hoạt động tương tự như số nguyên. Chỉ cần thêm hoặc bớt!
  • Để nhân hai số điểm cố định, nhân hai số sau đó dịch sang phải số bit phân số đã xác định.
  • Để chia hai số điểm cố định, dịch chuyển cổ tức sang số bit phân số đã xác định, sau đó chia cho số chia.
  • Chương bốn của bài viết này có hướng dẫn bổ sung về việc thực hiện các số điểm cố định nhị phân.

Số điểm cố định nhị phân có thể được triển khai trên bất kỳ loại dữ liệu số nguyên nào như int, long và BigInteger và các loại uint và ulong không tuân thủ CLS.

Như được đề xuất trong câu trả lời khác, bạn có thể sử dụng các bảng tra cứu, trong đó mỗi phần tử trong bảng là một số điểm cố định nhị phân, để giúp thực hiện các hàm phức tạp như sin, cosine, căn bậc hai, v.v. Nếu bảng tra cứu ít chi tiết hơn số điểm cố định, bạn nên làm tròn số đầu vào bằng cách thêm một nửa độ chi tiết của bảng tra cứu vào đầu vào:

// Assume each number has a 12 bit fractional part. (1/4096)
// Each entry in the lookup table corresponds to a fixed point number
//  with an 8-bit fractional part (1/256)
input+=(1<<3); // Add 2^3 for rounding purposes
input>>=4; // Shift right by 4 (to get 8-bit fractional part)
// --- clamp or restrict input here --
// Look up value.
return lookupTable[input];

5
Bạn nên tải nó lên một trang web dự án mã nguồn mở, như sourceforge hoặc github. Điều này giúp dễ tìm kiếm hơn, đóng góp dễ dàng hơn, dễ dàng đưa vào sơ yếu lý lịch của bạn, v.v. Ngoài ra, một vài mẹo về mã nguồn (hãy bỏ qua): Sử dụng constthay staticcho các hằng số, để trình biên dịch có thể tối ưu hóa chúng; thích các hàm thành viên hơn các hàm tĩnh (vì vậy chúng ta có thể gọi, ví dụ myDouble.LeadingZeros()thay vì IntDouble.LeadingZeros(myDouble)); cố gắng tránh các tên biến một chữ cái ( MultiplyAnyLengthví dụ: có 9, làm cho nó rất khó theo dõi)
BlueRaja - Danny Pflughoeft

Hãy cẩn thận sử dụng uncheckedvà không CLS-compliant loại như ulong, uintvv cho các mục đích tốc - bởi vì họ là như vậy hiếm khi được sử dụng, JIT không tối ưu hóa chúng như tích cực, vì vậy việc sử dụng chúng thực sự có thể chậm hơn so với sử dụng các loại bình thường như longint. Ngoài ra, C # có quá tải toán tử , dự án này sẽ được hưởng lợi rất nhiều. Cuối cùng, có bất kỳ bài kiểm tra đơn vị liên quan? Bên cạnh những điều nhỏ nhặt, công việc tuyệt vời Peter, điều này thật ấn tượng!
BlueRaja - Daniel Pflughoeft

Cảm ơn bạn đã cho ý kiến. Tôi thực hiện kiểm tra đơn vị trên mã. Chúng khá rộng rãi, mặc dù, quá nhiều để phát hành cho đến bây giờ. Tôi thậm chí còn viết các chương trình trợ giúp kiểm thử đơn vị để làm cho việc viết nhiều bài kiểm tra dễ dàng hơn. Bây giờ tôi không sử dụng các toán tử quá tải vì tôi có kế hoạch dịch mã sang Java khi tôi hoàn thành.
Peter O.

2
Điều buồn cười là khi tôi đăng lên blog của bạn, tôi đã không nhận thấy rằng blog là của bạn. Tôi vừa quyết định dùng thử google + và trong phần giới thiệu C # của nó, nó đã gợi ý mục blog đó. Vì vậy, tôi đã nghĩ "Thật là một sự trùng hợp đáng chú ý đối với hai chúng tôi khi bắt đầu viết một thứ như vậy cùng một lúc". Nhưng tất nhiên chúng tôi đã có cùng một kích hoạt :)
CodeInChaos

1
Tại sao phải chuyển cái này sang Java? Java đã đảm bảo toán học dấu phẩy động xác định thông qua strictfp.
Antimon

9

Đây có phải là một vấn đề cho C #?

Đúng. Kiến trúc khác nhau là những người ít lo lắng của bạn, khác nhau tốc độ khung hình vv có thể dẫn đến sai lệch do sai sót trong cơ quan đại diện phao - ngay cả khi họ là những giống không chính xác (ví dụ như kiến trúc giống nhau, ngoại trừ một GPU chậm hơn trên một máy).

Tôi có thể sử dụng System.Decimal không?

Không có lý do gì bạn không thể, tuy nhiên đó là con chó chậm.

Có cách nào để buộc chương trình của tôi chạy với độ chính xác gấp đôi không?

Đúng. Tự lưu trữ thời gian chạy CLR ; và biên dịch trong tất cả các lệnh / cờ không cần thiết (thay đổi hành vi của số học dấu phẩy động) vào ứng dụng C ++ trước khi gọi CorBindToR nbEx.

Có thư viện nào có thể giúp duy trì tính toán dấu phẩy động nhất quán không?

Không phải là tôi biết.

Có cách nào khác để giải quyết điều này?

Tôi đã giải quyết vấn đề này trước đây, ý tưởng là sử dụng QNumbers . Chúng là một dạng của các thực tế là điểm cố định; nhưng không cố định điểm trong cơ sở 10 (thập phân) - chứ không phải cơ sở 2 (nhị phân); bởi vì điều này, các nguyên hàm toán học trên chúng (add, sub, mul, div) nhanh hơn nhiều so với các điểm cố định cơ sở-10 ngây thơ; đặc biệt là nếu ngiống nhau cho cả hai giá trị (trong trường hợp của bạn là như vậy). Hơn nữa bởi vì chúng là tích hợp nên chúng có kết quả được xác định rõ trên mọi nền tảng.

Hãy nhớ rằng tốc độ khung hình vẫn có thể ảnh hưởng đến những điều này, nhưng nó không tệ và dễ dàng được khắc phục bằng cách sử dụng các điểm đồng bộ hóa.

Tôi có thể sử dụng nhiều hàm toán học hơn với QNumbers không?

Vâng, khứ hồi một thập phân để làm điều này. Hơn nữa, bạn thực sự nên sử dụng các bảng tra cứu cho các hàm trig (sin, cos); vì những cái đó thực sự có thể cho kết quả khác nhau trên các nền tảng khác nhau - và nếu bạn viết mã chính xác, chúng có thể sử dụng QNumbers trực tiếp.


3
Không chắc chắn những gì bạn đang nói với framerates là vấn đề. Rõ ràng bạn muốn có một tỷ lệ cập nhật cố định (xem ví dụ ở đây ) - cho dù điều đó có giống với tốc độ khung hình hiển thị hay không là không liên quan. Miễn là sự không chính xác là giống nhau trên tất cả các máy, chúng tôi đều tốt. Tôi không hiểu câu trả lời thứ ba của bạn cả.
BlueRaja - Daniel Pflughoeft

@BlueRaja: Câu trả lời "Có cách nào để buộc chương trình của tôi chạy với độ chính xác gấp đôi không?" sẽ là số tiền để thực hiện lại toàn bộ Thời gian chạy ngôn ngữ chung, điều này cực kỳ phức tạp hoặc sử dụng các cuộc gọi riêng tới DLL C ++ từ ứng dụng C #, như gợi ý trong câu trả lời của người dùng Shelleybutoston. Hãy nghĩ về "QNumbers" chỉ đơn thuần là số điểm cố định nhị phân, như được gợi ý trong câu trả lời của tôi (tôi chưa bao giờ thấy số điểm cố định nhị phân được gọi là "QNumbers".)
Peter O.

@Pieter O. Bạn không cần phải thực hiện lại thời gian chạy. Máy chủ tôi làm việc tại công ty của tôi lưu trữ thời gian chạy CLR dưới dạng ứng dụng C ++ gốc (SQL Server cũng vậy). Tôi đề nghị bạn google CorBindToRacerEx.
Jonathan Dickinson

@BlueRaja nó phụ thuộc vào trò chơi trong câu hỏi. Áp dụng các bước tốc độ khung hình cố định cho tất cả các trò chơi không phải là một lựa chọn khả thi - bởi vì thuật toán AOE đưa ra độ trễ nhân tạo; không thể chấp nhận được trong ví dụ FPS.
Jonathan Dickinson

1
@Jonathan: Đây chỉ là một vấn đề trong các trò chơi ngang hàng chỉ gửi đầu vào - đối với những trò chơi này, bạn phải có tỷ lệ cập nhật cố định. Hầu hết các FPS không hoạt động như thế này, nhưng một số ít nhất thiết phải có tốc độ cập nhật cố định. Xem câu hỏi này .
BlueRaja - Daniel Pflughoeft

6

Theo mục blog MSDN hơi cũ này , JIT sẽ không sử dụng SSE / SSE2 cho điểm nổi, tất cả đều là x87. Do đó, như bạn đã đề cập, bạn phải lo lắng về các chế độ và cờ và trong C # không thể kiểm soát. Vì vậy, sử dụng các hoạt động dấu phẩy động thông thường sẽ không đảm bảo kết quả chính xác giống nhau trên mọi máy cho chương trình của bạn.

Để có được độ tái lập chính xác của độ chính xác kép, bạn sẽ phải thực hiện mô phỏng phần mềm dấu phẩy động (hoặc điểm cố định). Tôi không biết các thư viện C # để làm điều này.

Tùy thuộc vào các hoạt động bạn cần, bạn có thể có thể thoát khỏi với độ chính xác duy nhất. Đây là ý tưởng:

  • lưu trữ tất cả các giá trị bạn quan tâm trong một độ chính xác duy nhất
  • để thực hiện một thao tác:
    • mở rộng đầu vào để tăng gấp đôi độ chính xác
    • hoạt động với độ chính xác gấp đôi
    • chuyển đổi kết quả trở lại chính xác

Vấn đề lớn với x87 là các tính toán có thể được thực hiện với độ chính xác 53 bit hoặc 64 bit tùy thuộc vào cờ chính xác và liệu thanh ghi có bị đổ vào bộ nhớ hay không. Nhưng đối với nhiều thao tác, thực hiện thao tác với độ chính xác cao và làm tròn trở lại độ chính xác thấp hơn sẽ đảm bảo câu trả lời chính xác, ngụ ý rằng câu trả lời sẽ được đảm bảo giống nhau trên tất cả các hệ thống. Cho dù bạn có độ chính xác cao hơn sẽ không thành vấn đề, vì bạn có đủ độ chính xác để đảm bảo câu trả lời đúng trong cả hai trường hợp.

Các hoạt động nên hoạt động trong sơ đồ này: cộng, trừ, nhân, chia, sqrt. Những thứ như sin, exp, v.v. sẽ không hoạt động (kết quả thường sẽ khớp nhưng không có gì đảm bảo). "Khi nào làm tròn đôi vô hại?" Tham chiếu ACM (trả phí reg. Req.)

Hi vọng điêu nay co ich!


2
Đây cũng là một vấn đề mà .NET 5, hoặc 6 hoặc 42, có thể không sử dụng chế độ tính toán x87 nữa. Không có gì trong tiêu chuẩn đòi hỏi nó phải.
Eric J.

5

Như đã được nêu bởi các câu trả lời khác: Có, đây là một vấn đề trong C # - ngay cả khi vẫn giữ nguyên Windows.

Đối với giải pháp: Bạn có thể giảm (và với một số nỗ lực / hiệu suất) tránh hoàn toàn vấn đề nếu bạn sử dụng BigIntegerlớp tích hợp và chia tỷ lệ tất cả các phép tính thành độ chính xác xác định bằng cách sử dụng mẫu số chung cho bất kỳ phép tính / lưu trữ các số đó.

Theo yêu cầu của OP - liên quan đến hiệu suất:

System.Decimalđại diện cho số có 1 bit cho một dấu và Số nguyên 96 bit và "tỷ lệ" (biểu thị vị trí của dấu thập phân). Đối với tất cả các tính toán bạn thực hiện, nó phải hoạt động trên cấu trúc dữ liệu này và không thể sử dụng bất kỳ hướng dẫn dấu phẩy động nào được tích hợp trong CPU.

Các BigInteger"giải pháp" làm điều gì đó tương tự - duy nhất mà bạn có thể xác định có bao nhiêu chữ số bạn cần / muốn ... có lẽ bạn muốn chỉ có 80 bit hoặc 240 bit chính xác.

Sự chậm chạp luôn xuất phát từ việc phải mô phỏng tất cả các hoạt động trên các số này thông qua các hướng dẫn chỉ có số nguyên mà không sử dụng các hướng dẫn tích hợp CPU / FPU, từ đó dẫn đến nhiều hướng dẫn hơn cho mỗi thao tác toán học.

Để giảm hiệu suất đạt được, có một số chiến lược - như QNumbers (xem câu trả lời từ Jonathan Dickinson - Toán học dấu phẩy động có nhất quán trong C # không? ) Và / hoặc bộ nhớ đệm (ví dụ: tính toán trig ...), v.v.


1
Lưu ý rằng BigIntegerchỉ có sẵn trong .Net 4.0.
Svick

Tôi đoán là hiệu suất đạt được BigIntegervượt quá cả hiệu suất của Decimal.
CodeInChaos

Một vài lần trong các câu trả lời ở đây có tham khảo về hiệu suất sử dụng Decimal(@Jonathan Dickinson - 'chó chậm') hoặc BigInteger(@CodeInChaos nhận xét ở trên) - ai đó có thể vui lòng cung cấp một chút giải thích về các lần truy cập hiệu suất này hay không / tại sao họ thực sự là những người ngăn chặn để cung cấp một giải pháp.
Barry Kaye

@Yahia - cảm ơn bạn đã chỉnh sửa - tuy nhiên, việc đọc thú vị, bạn có thể vui lòng chỉ đưa ra một dự đoán về công viên bóng về hiệu suất của việc không sử dụng 'float' là chúng ta nói chậm hơn 10% hoặc chậm hơn 10 lần - Tôi chỉ nói muốn có được một cảm giác cho thứ tự cường độ ngụ ý.
Barry Kaye

nó thích hơn ở khu vực 1: 5 hơn là "chỉ 10%"
Yahia

2

Chà, đây sẽ là nỗ lực đầu tiên của tôi về cách làm điều này :

  1. Tạo một dự án ATL.dll có một đối tượng đơn giản để sử dụng cho các hoạt động điểm nổi quan trọng của bạn. đảm bảo biên dịch nó với các cờ vô hiệu hóa bằng cách sử dụng bất kỳ phần cứng không phải xx87 nào để thực hiện dấu phẩy động.
  2. Tạo các hàm gọi các phép toán dấu phẩy động và trả về kết quả; bắt đầu đơn giản và sau đó nếu nó hiệu quả với bạn, bạn luôn có thể tăng độ phức tạp để đáp ứng nhu cầu hiệu suất của bạn sau này nếu cần thiết.
  3. Đặt các lệnh gọi control_fp xung quanh toán học thực tế để đảm bảo rằng nó được thực hiện theo cách tương tự trên tất cả các máy.
  4. Tham khảo thư viện mới của bạn và kiểm tra để đảm bảo nó hoạt động như mong đợi.

(Tôi tin rằng bạn chỉ có thể biên dịch thành một tệp 32 bit và sau đó sử dụng nó với x86 hoặc AnyCpu [hoặc có thể chỉ nhắm mục tiêu x86 trên hệ thống 64 bit; xem bình luận bên dưới].)

Sau đó, giả sử nó hoạt động, bạn có muốn sử dụng Mono không, tôi tưởng tượng bạn có thể sao chép thư viện trên các nền tảng x86 khác theo cách tương tự (tất nhiên không phải là COM; mặc dù, có lẽ, với rượu vang một chút chúng tôi đến đó mặc dù ...).

Giả sử bạn có thể làm cho nó hoạt động, bạn sẽ có thể thiết lập các chức năng tùy chỉnh có thể thực hiện nhiều thao tác cùng một lúc để khắc phục mọi sự cố về hiệu suất và bạn sẽ có phép toán dấu phẩy động cho phép bạn có kết quả nhất quán trên các nền tảng với số lượng tối thiểu mã được viết bằng C ++ và để phần còn lại của mã trong C #.


"Biên dịch thành một tệp 32 bit và sau đó sử dụng ... AnyCpu" Tôi nghĩ rằng điều này sẽ chỉ hoạt động khi chạy trên hệ thống 32 bit. Trên hệ thống 64 bit, chỉ một chương trình nhắm mục tiêu x86sẽ có thể tải dll 32 bit.
CodeInChaos

2

Tôi không phải là nhà phát triển trò chơi, mặc dù tôi có nhiều kinh nghiệm với các vấn đề khó tính toán ... vì vậy, tôi sẽ làm hết sức mình.

Chiến lược tôi sẽ áp dụng về cơ bản là:

  • Sử dụng phương pháp chậm hơn (nếu cần thiết; nếu có cách nhanh hơn, tuyệt vời!), Nhưng phương pháp có thể dự đoán được để có kết quả có thể lặp lại
  • Sử dụng gấp đôi cho mọi thứ khác (ví dụ: kết xuất)

Cái ngắn của việc này là: bạn cần tìm sự cân bằng. Nếu bạn đang sử dụng kết xuất 30ms (~ 33 khung hình / giây) và chỉ 1ms khi thực hiện phát hiện va chạm (hoặc chèn một số thao tác có độ nhạy cao khác) - ngay cả khi bạn tăng gấp ba thời gian để thực hiện số học quan trọng, tác động của nó đối với tốc độ khung hình của bạn là bạn giảm từ 33.3fps xuống 30.3fps.

Tôi đề nghị bạn lập hồ sơ mọi thứ, tính toán thời gian dành cho mỗi phép tính đắt đỏ đáng chú ý, sau đó lặp lại các phép đo với 1 hoặc nhiều phương pháp để giải quyết vấn đề này và xem tác động là gì.


1

Kiểm tra các liên kết trong các câu trả lời khác cho thấy rõ ràng bạn sẽ không bao giờ đảm bảo liệu dấu phẩy động được thực hiện "chính xác" hay liệu bạn sẽ luôn nhận được độ chính xác nhất định cho một phép tính nhất định, nhưng có lẽ bạn có thể nỗ lực hết sức bằng cách (1) cắt ngắn tất cả các phép tính đến mức tối thiểu chung (ví dụ: nếu việc triển khai khác nhau sẽ mang lại cho bạn độ chính xác từ 32 đến 80 bit, luôn cắt ngắn mọi thao tác thành 30 hoặc 31 bit), (2) có một bảng gồm một vài trường hợp thử nghiệm khi khởi động (các trường hợp đường biên của cộng, trừ, nhân, chia, sqrt, cosine, v.v.) và nếu việc thực hiện tính toán các giá trị khớp với bảng thì không cần thực hiện bất kỳ điều chỉnh nào.


luôn cắt ngắn mọi thao tác xuống còn 30 hoặc 31 bit - đây chính xác là những gì floatkiểu dữ liệu thực hiện trên các máy x86 - tuy nhiên điều này sẽ gây ra kết quả hơi khác so với các máy thực hiện tất cả các phép tính của chúng chỉ sử dụng 32 bit và những thay đổi nhỏ này sẽ lan truyền theo thời gian. Do đó, câu hỏi.
BlueRaja - Daniel Pflughoeft

Nếu "N bit chính xác" có nghĩa là bất kỳ phép tính nào đều chính xác với nhiều bit đó và máy A chính xác đến 32 bit trong khi máy B chính xác đến 48 bit, thì 32 bit đầu tiên của bất kỳ calc nào của cả hai máy đều phải giống hệt nhau. Sẽ không cắt ngắn đến 32 bit hoặc ít hơn sau mỗi hoạt động giữ cho cả hai máy đồng bộ chính xác? Nếu không, ví dụ là gì?
ID bảo vệ nhân chứng 44583292

-3

Câu hỏi của bạn trong những thứ khá khó khăn và kỹ thuật O_o. Tuy nhiên tôi có thể có một ý tưởng.

Bạn chắc chắn biết rằng CPU thực hiện một số điều chỉnh sau bất kỳ hoạt động nổi nào. Và CPU cung cấp một số hướng dẫn khác nhau làm cho hoạt động làm tròn khác nhau.

Vì vậy, đối với một biểu thức, trình biên dịch của bạn sẽ chọn một tập hợp các hướng dẫn dẫn đến kết quả. Nhưng bất kỳ quy trình hướng dẫn nào khác, ngay cả khi họ có ý định tính cùng một biểu thức, có thể cung cấp một kết quả khác.

'Những sai lầm' được thực hiện bởi một điều chỉnh làm tròn sẽ phát triển theo từng hướng dẫn thêm.

Như một ví dụ, chúng ta có thể nói rằng ở cấp độ lắp ráp: a * b * c không tương đương với a * c * b.

Tôi không hoàn toàn chắc chắn về điều đó, bạn sẽ cần phải hỏi một người biết kiến ​​trúc CPU nhiều hơn tôi: p

Tuy nhiên, để trả lời câu hỏi của bạn: trong C hoặc C ++, bạn có thể giải quyết vấn đề của mình vì bạn có một số kiểm soát đối với mã máy do trình biên dịch của bạn tạo ra, tuy nhiên trong .NET bạn không có bất kỳ. Vì vậy, miễn là mã máy của bạn có thể khác, bạn sẽ không bao giờ chắc chắn về kết quả chính xác.

Tôi tò mò về cách điều này có thể là một vấn đề bởi vì biến thể có vẻ rất nhỏ, nhưng nếu bạn cần thao tác thực sự chính xác, giải pháp duy nhất tôi có thể nghĩ đến là tăng kích thước của các thanh ghi nổi của bạn. Sử dụng độ chính xác gấp đôi hoặc thậm chí dài gấp đôi nếu bạn có thể (không chắc chắn có thể sử dụng CLI).

Tôi hy vọng tôi đã đủ rõ ràng, tôi không hoàn hảo bằng tiếng Anh (... tất cả: s)


9
Hãy tưởng tượng một game bắn súng P2P. Bạn bắn vào một anh chàng, bạn đánh anh ta và anh ta chết, nhưng nó rất gần, bạn gần như bỏ lỡ. Trên PC của người kia sử dụng các tính toán hơi khác nhau và nó tính toán mà bạn bỏ lỡ. Bạn có thấy vấn đề bây giờ không? Trong trường hợp này, việc tăng kích thước của các thanh ghi sẽ không giúp ích (ít nhất là không hoàn toàn). Sử dụng tính toán chính xác tương tự trên mỗi máy tính sẽ.
Svick

5
Trong một kịch bản này thường không quan tâm đến khoảng cách giữa kết quả là đến kết quả thực tế (miễn là nó là hợp lý), nhưng điều quan trọng là nó chính xác như nhau cho tất cả người dùng.
CodeInChaos

1
Bạn nói đúng, tôi đã không nghĩ về loại kịch bản này. Tuy nhiên tôi đồng ý với @CodeInChaos về điều này. Tôi đã không thấy rằng thực sự thông minh để đưa ra một quyết định quan trọng hai lần. Đây là một vấn đề kiến ​​trúc phần mềm nhiều hơn. Một chương trình, ứng dụng của game bắn súng mẫu mực, nên thực hiện tính toán và gửi kết quả cho những người khác. Bạn sẽ không bao giờ có lỗi theo cách này. Bạn có một hit hay không, nhưng chỉ có một lần tuyệt vọng. Giống như nói @driushkin
AxFab

2
@Aesgar: Vâng, đó là cách mà hầu hết các game bắn súng hoạt động; "thẩm quyền" đó được gọi là máy chủ và chúng tôi gọi kiến ​​trúc tổng thể là kiến ​​trúc "máy khách / máy chủ". Tuy nhiên, có một loại kiến ​​trúc khác: ngang hàng. Trong P2P, không có máy chủ; thay vào đó, tất cả các khách hàng phải xác minh tất cả các hành động với nhau trước khi bất cứ điều gì xảy ra. Điều này làm tăng độ trễ, khiến game bắn súng không thể chấp nhận được, nhưng làm giảm đáng kể lưu lượng mạng, khiến nó trở nên hoàn hảo cho các trò chơi có độ trễ nhỏ (~ 250ms), nhưng đồng bộ hóa toàn bộ trạng thái trò chơi thì không. Cụ thể, các trò chơi RTS như C & C và Starcraft sử dụng P2P.
BlueRaja - Daniel Pflughoeft

5
Trong trò chơi p2p, bạn không có máy đáng tin cậy để dựa vào. Nếu bạn cho phép một trạm quyết định liệu viên đạn của anh ta có bắn trúng hay không, bạn sẽ mở ra khả năng khách hàng gian lận. Hơn nữa, các liên kết thậm chí không thể xử lý lượng dữ liệu đôi khi có kết quả - các trò chơi hoạt động bằng cách gửi đơn đặt hàng thay vì kết quả. Tôi chơi các trò chơi RTS và nhiều lần tôi đã thấy rất nhiều thứ linh tinh bay xung quanh không có cách nào nó có thể được gửi qua các liên kết gia đình thông thường.
Loren Pechtel
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.