Tại sao phép chia modulus (%) chỉ hoạt động với số nguyên?


79

Gần đây tôi đã gặp phải một vấn đề có thể dễ dàng giải quyết bằng cách sử dụng phân chia mô-đun, nhưng đầu vào là một float:

Cho một hàm tuần hoàn (ví dụ sin) và một hàm máy tính chỉ có thể tính toán nó trong phạm vi chu kỳ (ví dụ [-π, π]), hãy tạo một hàm có thể xử lý bất kỳ đầu vào nào.

Giải pháp "hiển nhiên" là một cái gì đó như:

#include <cmath>

float sin(float x){
    return limited_sin((x + M_PI) % (2 *M_PI) - M_PI);
}

Tại sao điều này không hoạt động? Tôi gặp lỗi này:

error: invalid operands of types double and double to binary operator %

Điều thú vị là nó hoạt động bằng Python:

def sin(x):
    return limited_sin((x + math.pi) % (2 * math.pi) - math.pi)

20
π không bằng 3,14 và trên thực tế, nó không thể biểu diễn như bất kỳ kiểu dấu phẩy động nào. Tính toán sin(x)các giá trị lớn xthực sự đòi hỏi một quá trình giảm đối số siêu nghiệm rất khó khăn mà không thể thực hiện được với bất kỳ xấp xỉ hữu hạn nào của pi.
R .. GitHub DỪNG TRỢ GIÚP TỪ NGÀY

3
Đây gần như chắc chắn là một bài tập về nhà, vì vậy lỗi dấu chấm động nằm ngoài phạm vi của bài tập, hoặc điều này có nghĩa là dẫn đến một cuộc thảo luận về phân tích số chặt chẽ hơn. Dù bằng cách nào, fmodcó thể là những gì người hướng dẫn đang tìm kiếm.
Dennis Zickefoose

2
Nó không phải là bài tập về nhà, nó chỉ là một cái gì đó xuất hiện trong khi đọc một câu hỏi SO khác ( stackoverflow.com/questions/6091837/… )
Brendan Long

2
OK, lẽ ra tôi phải chính xác hơn trong tuyên bố của mình. Quan điểm của tôi là nếu đối số có thể phát triển lớn không giới hạn (không chỉ kích thước số mũ chính xác gấp đôi), thì không có xấp xỉ hữu hạn nào của số pi là đủ. Đối với gấp đôi, vâng, một xấp xỉ rất dài của số pi sẽ là đủ.
R .. GitHub DỪNG TRỢ GIÚP NGAY LÚC NÀY

1
@aschepler: Tôi không nghĩ rằng bạn đã hiểu vấn đề.
R .. GitHub DỪNG TRỢ GIÚP ICE

Câu trả lời:


76

Bởi vì khái niệm toán học thông thường về "phần dư" chỉ có thể áp dụng cho phép chia số nguyên. tức là phép chia được yêu cầu để tạo ra thương số nguyên.

Để mở rộng khái niệm "phần dư" thành số thực, bạn phải giới thiệu một loại phép toán "kết hợp" mới sẽ tạo ra thương số nguyên cho các toán hạng thực . Ngôn ngữ Core C không hỗ trợ hoạt động như vậy, nhưng nó được cung cấp như một fmodchức năng thư viện tiêu chuẩn , cũng như remainderchức năng trong C99. (Lưu ý rằng các hàm này không giống nhau và có một số đặc biệt. Đặc biệt, chúng không tuân theo quy tắc làm tròn của phép chia số nguyên.)


7
Về giá trị, theo định nghĩa của% trong tiêu chuẩn 98: "(a / b) * b + a% b bằng a." Đối với các kiểu dấu phẩy động, (a/b)*bđã bằng a[trong chừng mực có thể tạo ra một câu lệnh như vậy cho các kiểu dấu phẩy động], do đó a%bsẽ không bao giờ đặc biệt hữu ích.
Dennis Zickefoose

1
@Dennis: Thật vậy, về mặt đại số, trong một trường, phần dư luôn bằng 0. Định nghĩa thích hợp nhất của %toán tử cho dấu phẩy động, tôi cho là a-(a/b)*b, sẽ là 0 hoặc một giá trị rất nhỏ.
R .. GitHub NGỪNG TRỢ GIÚP ICE

7
@Dennis: Bạn có thể dễ dàng sửa công thức này bằng cách yêu cầu "tầng (a / b) * b + a% b = a". Lưu ý rằng đối với số nguyên, tầng (a / b) = a / b.
vog

Phép chia số nguyên kiểu C sử dụng trunc, không sử dụng tầng, nhưng điểm vẫn còn.
dan04

18
-1 Re "khái niệm toán học bình thường về" phần dư "chỉ áp dụng cho phép chia số nguyên", khái niệm toán học về số học modulo cũng hoạt động với các giá trị dấu phẩy động, và đây là một trong những vấn đề đầu tiên mà Donald Knuth thảo luận trong cuốn sách kinh điển của ông . Thuật Lập Trình Máy Tính (tập I). Tức là nó đã từng là kiến ​​thức cơ bản. Ngày nay, học sinh không nhận được sự giáo dục mà họ phải trả, IMHO.
Chúc mừng và hth. - Alf

52

Bạn đang tìm kiếm fmod () .

Tôi đoán để trả lời cụ thể hơn câu hỏi của bạn, trong các ngôn ngữ cũ hơn, %toán tử chỉ được định nghĩa là phép chia mô-đun số nguyên và trong các ngôn ngữ mới hơn, họ quyết định mở rộng định nghĩa của toán tử.

CHỈNH SỬA: Nếu tôi đặt cược đoán tại sao, tôi sẽ nói đó là vì ý tưởng về số học mô-đun bắt nguồn từ lý thuyết số và giải quyết cụ thể với số nguyên.


9
"ngôn ngữ cũ hơn" - APL có từ những năm 1960 và toán tử modulo của nó "|" hoạt động với cả số nguyên và dữ liệu dấu phẩy động (cũng như vô hướng, vectơ, ma trận, tensor, ...). Không có lý do chính đáng nào mà toán tử modulo "%" của C không thể thực hiện chức năng tương tự như fmod nếu được sử dụng với số dấu phẩy động.
rcgldr

@rcgldr Mục tiêu thiết kế không yêu cầu modulo dấu phẩy động. C được thực hiện để biên dịch Unix và để giới hạn số lượng hợp ngữ cần thiết cho hệ điều hành. "C là một ngôn ngữ thủ tục mệnh lệnh. Nó được thiết kế để biên dịch bằng một trình biên dịch tương đối đơn giản, để cung cấp quyền truy cập cấp thấp vào bộ nhớ, cung cấp các cấu trúc ngôn ngữ ánh xạ hiệu quả đến các lệnh máy và yêu cầu hỗ trợ thời gian chạy tối thiểu." vi.wikipedia.org/wiki/C_(programming_language)
harper

1
@harper - Vì C bao gồm số học dấu phẩy động như cộng, trừ, nhân và chia, sử dụng cùng một cú pháp mà nó làm cho số nguyên, tôi không hiểu tại sao nó cũng không thể bao gồm modulo bằng cách sử dụng cùng một cú pháp (%) . Lựa chọn bao gồm nó hoặc không có vẻ tùy ý.
rcgldr

16

Tôi thực sự không thể nói chắc chắn , nhưng tôi đoán nó chủ yếu là lịch sử. Khá nhiều trình biên dịch C đầu tiên không hỗ trợ dấu phẩy động. Nó đã được thêm vào sau đó, và thậm chí sau đó không hoàn toàn - chủ yếu là kiểu dữ liệu đã được thêm vào và các hoạt động nguyên thủy nhất được hỗ trợ trong ngôn ngữ này, nhưng mọi thứ khác được để lại cho thư viện chuẩn.


1
+1 vì là câu trả lời hợp lý đầu tiên mà tôi thấy khi đang đọc danh sách. Trên thực tế, bây giờ đã đọc tất cả, đây là câu trả lời hợp lý duy nhất .
Chúc mừng và hth. - Alf

Một +1 muộn màng từ tôi cũng vậy. Tôi đã từng viết bằng C cho hệ thống nhúng 6809 và Z80. Không cách nào tôi có thể đủ không gian để bao gồm thư viện thời gian chạy c. Thậm chí đã phải viết mã khởi động của riêng tôi. Floating point là một sự xa xỉ tôi không thể đủ khả năng :)
Richard Hodges

12

Toán tử modulo %trong C và C ++ được định nghĩa cho hai số nguyên, tuy nhiên, có một fmod()hàm có sẵn để sử dụng với hàm nhân đôi.


4
Đây là câu trả lời cho câu hỏi của OP, nhưng bỏ qua vấn đề cơ bản trong những gì OP đang cố gắng làm: sin(fmod(x,3.14))hoặc thậm chí sin(fmod(x,M_PI))không bằng sin(x)với các giá trị lớn của x. Trên thực tế, các giá trị có thể khác nhau tối đa là 2,0.
R .. GitHub NGỪNG TRỢ GIÚP TỪ NGÀY

2
@R ..: Đúng, nhưng đó là một câu hỏi khác và tôi không hoàn toàn chắc chắn có một câu trả lời được chấp nhận, mặc dù có rất nhiều nghiên cứu về chủ đề này
Mark Elliot

@R - Tôi đã sửa phương trình để làm đúng. Phương trình thực tế không phải là vấn đề (khá dễ dàng để tìm ra một khi tôi có chức năng kiểm tra nó).
Brendan Long

Không phải là %toán tử phần dư và không phải là toán tử modulo?
chux - Phục hồi Monica

7

Các ràng buộc nằm trong các tiêu chuẩn:

C11 (ISO / IEC 9899: 201x) §6.5.5 Toán tử phép nhân

Mỗi toán hạng sẽ có kiểu số học. Các toán hạng của toán tử% sẽ có kiểu số nguyên.

C ++ 11 (ISO / IEC 14882: 2011) §5.6 Các toán tử nhân

Các toán hạng của * và / sẽ có kiểu số học hoặc kiểu liệt kê; các toán hạng% phải có kiểu tích phân hoặc kiểu liệt kê. Các phép chuyển đổi số học thông thường được thực hiện trên các toán hạng và xác định loại kết quả.

Giải pháp là sử dụng fmod, đó là lý do chính xác tại sao các toán hạng của %bị giới hạn ở kiểu số nguyên ngay từ đầu, theo Cơ sở lý luận C99 §6.5.5 Các toán tử nhân :

Ủy ban C89 bác bỏ việc mở rộng toán tử% để làm việc trên các kiểu thả nổi vì việc sử dụng như vậy sẽ trùng lặp với cơ sở do fmod cung cấp



2

Toán tử% cung cấp cho bạn NHẬN XÉT (tên khác của mô đun) của một số. Đối với C / C ++, điều này chỉ được xác định cho các hoạt động số nguyên. Python rộng hơn một chút và cho phép bạn lấy phần còn lại của một số dấu phẩy động cho phần còn lại của số lần có thể được chia cho nó:

>>> 4 % math.pi
0.85840734641020688
>>> 4 - math.pi
0.85840734641020688
>>> 

2
Phần còn lại không phải là 'tên khác của modulus' !! Xem: stackoverflow.com/questions/13683563/… hoặc từ math-pov: math.stackexchange.com/questions/801962/… Nói một cách đơn giản: modulo và phần dư chỉ giống nhau đối với số dương và một ví dụ khác là phần còn lại không ' t để bạn đi xung quanh la bàn (ngược chiều kim đồng hồ). Xin vui lòng sửa mà như tôi sáng đến căn cơ để downvote:P
GitaarLAB

2

Các %nhà điều hành không hoạt động trong C ++, khi bạn đang cố gắng tìm thời gian còn lại của hai số đó là cả hai loại Floathoặc Double.

Do đó, bạn có thể thử sử dụng fmodhàm từ math.h/ cmath.hhoặc bạn có thể sử dụng các dòng mã này để tránh sử dụng tệp tiêu đề đó:

float sin(float x) {
 float temp;
 temp = (x + M_PI) / ((2 *M_PI) - M_PI);
 return limited_sin((x + M_PI) - ((2 *M_PI) - M_PI) * temp ));

}


1

"Khái niệm toán học về số học modulo cũng có tác dụng đối với các giá trị dấu phẩy động, và đây là một trong những vấn đề đầu tiên Donald Knuth thảo luận trong cuốn sách kinh điển Nghệ thuật lập trình máy tính (tập I) của ông. Tức là nó đã từng là kiến ​​thức cơ bản."

Toán tử mô-đun dấu phẩy động được định nghĩa như sau:

m = num - iquot*den ; where iquot = int( num/den )

Như đã chỉ ra, việc bỏ chọn toán tử% trên số dấu phẩy động dường như có liên quan đến các tiêu chuẩn. CRTL cung cấp 'fmod' và thường là 'phần dư' để thực hiện% trên số fp. Sự khác biệt giữa hai điều này nằm ở cách chúng xử lý làm tròn 'iquot' trung gian.

'còn lại' sử dụng làm tròn đến gần nhất và 'fmod' sử dụng cắt ngắn đơn giản.

Nếu bạn viết các lớp số C ++ của riêng mình, không có gì ngăn cản bạn sửa đổi kế thừa no-op, bằng cách bao gồm toán tử% bị quá tải.

Trân trọng

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.