Mã của câu hỏi tính toán 64-bit lấy<2>(tôi)
=$h:=f\,g\bmod n$ từ đầu vào:
- 64-bit
mô đun_giá trị
=$n$ với $n\in[2,\,2^{63}]$
- 64-bit
lấy<0>(I)
=$f$ với $f\in[0,\,2^{64}-1]$
- 64-bit
nhận được<1>(I)
=$g$ với $g\in[0,\,2^{64}-1]$ và $f\,g<2^{64}\,n$, một điều kiện được đáp ứng nếu $f,g\in[0,\,n-1]$ (mà tôi đoán luôn là trường hợp trong ứng dụng).
Kết quả $h$ là phần còn lại của phép chia Euclide $f\,g$ qua $n$. Nó được định nghĩa toán học bởi $0\le h<m$ và $\exists q\in\mathbb Z,\ f\,g=q\cdot n+h$.
Mã đầu tiên tính toán 128-bit z
$=z:=f\,g$, thì kết quả lấy<2>(tôi)
$=h:=z\bmod n$ qua giảm barrett:
- Nó đã được tính toán trước (bên ngoài mã của câu hỏi)
const_ratio
$=r=\left\lfloor2^{128}/n\right\rfloor$
- $\hat q:=\left\lfloor z\,r/2^{128}\right\rfloor$, đó là chính xác $q$ trong một theo mặc định (lưu ý: $\hat q$ là giá trị cuối cùng của biến
tmp1
).
- $\hat h:=z-q\cdot n$, đó là chính xác $h$ trong khả năng vượt quá chính xác $n$ (Ghi chú: $\hat h$ là giá trị cuối cùng của biến
tmp3
).
- $h:=\hat h-n$ khi nào $\hat h\ge n$, hoặc $h:=\hat h$ nếu không thì.
Mã này sử dụng các thuật toán của trường tiểu học để thực hiện phép tính nhiều chữ số, được chuyển đổi từ cơ số $10$ để căn cứ $2^{64}$ (với các tối ưu hóa và một biến thể được trình bày chi tiết trong phần tiếp theo). Tương đương với các chữ số được gọi là chân tay, ở đây 64-bit.
Sản phẩm z
$=z$ được thể hiện như hai chi z[0]
$=z_0$ (bậc thấp), z[1]
$=z_1$ (bậc cao), do đó với $z=z_0+2^{64}\,z_1$, và $z_0, z_1\in[0,\,2^{64}-1]$.
Hệ số nhân Barrett const_ratio
$=r=\left\lfloor2^{128}/n\right\rfloor$ được thể hiện tương tự như hai chi const_ratio_0
$=r_0$ và const_ratio_1
$=r_1$, nhờ vào mức thấp nhất của khoảng thời gian trong điều kiện tiên quyết $n\in[2,\,2^{63}]$.
Sản phẩm trung gian $z\,r$ luôn phù hợp với ba chi (mặc dù nói chung, tích của hai đại lượng được biểu thị bằng hai chi sẽ yêu cầu bốn chi).
Thương số dự kiến theo mặc định $\hat q$ phù hợp với một chi duy nhất, kể từ khi $\hat q\le q$ và $q<2^{64}$, với điều kiện tiên quyết đầu vào được bảo hiểm mới nhất $f\,g<2^{64}\,n$.
Phần còn lại dự kiến $\hat h$ phù hợp với một chi duy nhất, kể từ khi $\hat h<2n$ và $2n\le2^{64}$, với phần sau nhờ vào phần cuối cao của khoảng trong điều kiện tiên quyết $n\in[2,\,2^{63}]$.
Chi tiết thuật toán của mã (như đã hỏi trong bình luận và tiền thưởng):
Tôi sẽ sử dụng một hình minh họa ở dạng thập phân. Trong cơ sở đó, kể từ khi $2\le n\le10/2$, const_ratio
$=r$ chỉ có thể là $\left\lfloor100/2\right\rfloor=50$, $\left\lfloor100/3\right\rfloor=33$, $\left\lfloor100/4\right\rfloor=25$, $\left\lfloor100/5\right\rfloor=20$, nhưng tôi sẽ giả vờ const_ratio
$=r=29$ bởi vì điều đó làm cho một ví dụ thú vị hơn. Vì lý do tương tự tôi sẽ sử dụng z
$=z=34$, mặc dù không thể lấy tích đó dưới dạng tích của hai chữ số.
Sản phẩm z
được lấy trong mã bởi multi_uint64(get<0>(I), get<1>(I), z)
như hai chi z[0]
và z[1]
.
Thịt của tính toán là $\hat q:=\left\lfloor z\,r/2^{128}\right\rfloor$. Đó là tương tự trong cơ sở $2^{64}$ của $9:=\left\lfloor29\cdot34/100\right\rfloor$ trong cơ sở 10. Cả hai đối số $29$ và $34$ đến phép nhân có hai chữ số, nhưng đủ nhỏ để tích của chúng $986$ là ba chữ số (thay vì bốn) và chúng tôi chỉ quan tâm đến chữ số thứ ba từ bên phải. Thuật toán trường tiểu học để tính toán $986:=29\cdot34$ sẽ được trình bày như
2 9 const_ratio
x 3 4 z
-----
1 1 6
+ 8 7
-------
9 8 6
Trong thuật toán của trường tiểu học, có bốn phép nhân một chữ số (mà mã thực hiện) và một vài thao tác bổ sung (mã sắp xếp lại một chút):
4
lần 9
, 36
; viết 6
, giữ 3
;
4
lần 2
, 8
; thêm 3
(đã giữ), 11
; viết đó.
3
lần 9
, 27
; viết 7
, giữ 2
;
3
lần 2
, 6
; thêm 2
(đã giữ), 8
; viết đó.
Phép nhân đầu tiên trong bốn phép nhân này xảy ra trong đoạn mã multi_uint64_hw64(z[0], const_ratio_0, &carry)
, nhân với chi bậc thấp của $r$ với chi bậc thấp của $z$, giống như chúng ta nhân chữ số bậc thấp 4
của 34
với chữ số bậc thấp 9
của 29
. Lưu ý rằng "viết 6
" là vô nghĩa trong trường hợp này, vì bất kỳ chữ số nào nó viết sẽ được tách biệt sang cột bên phải của phép tính mà không có bất kỳ cơ hội nào để tác động đến chữ số ngoài cùng bên trái và bị bỏ qua khi chúng ta chia cho 100 và làm tròn xuống (tương đương, chỉ giữ lại chữ số thứ ba từ đúng). Đó là lý do tại sao 64-bit bậc thấp của sản phẩm 128-bit thậm chí không được tính toán, như đã lưu ý trong câu hỏi. Tương đương với 3
Trong 36
được giữ trong mang
.
Phép nhân thứ hai xảy ra trong multi_uint64(z[0], const_ratio_1, tmp2)
, nhân với chi bậc cao của $r$ với chi bậc thấp của $z$, với kết quả là hai nhánh của tmp2
; 64-bit tmp[0]
nhận tương đương với 8
Trong 8
, và tmp[1]
nhận tương đương với 0
vì
(chú ý một hàng đầu 0
bị loại bỏ trong cách viết thông thường của số nguyên thập phân). tương đương với 8 cộng 3
xảy ra trong add_uint64(tmp2[0], mang theo, &tmp1)
, với chữ số bậc thấp 1
của kết quả 11
Trong tmp1
, và carry mới 1
trong đầu ra của chức năng đó. Điều đó được sử dụng như toán hạng bên phải trong tmp3 = tmp2[1] + â¦
(điều này tình cờ bị bỏ qua trong thuật toán của trường tiểu học với ví dụ cụ thể mà tôi đã lấy từ 0
đã bị triệt tiêu), mang lại kết quả tương đương với bên trái 1
Trong 116
. [Lưu ý về đầu ra của add_uint64
: nó được tạo ra bởi static_cast<unsigned char>(*result < toán hạng1)
, so sánh *kết quả
và toán hạng
, sau đó quay thật
đến 1
, sai
đến 0
. thực hiện sau *kết quả = toán hạng1 + toán hạng2
, điều đó cho biết liệu phần bổ sung này có tạo ra một lần thực hiện hay không. Một số trình biên dịch nhận ra thành ngữ này, sử dụng bit C của từ trạng thái và sử dụng lại C trong phần bổ sung sắp tới].
Phép nhân thứ ba xảy ra trong multi_uint64(z[1], const_ratio_0, tmp2)
, nhân với chi bậc thấp của $r$ với chi bậc cao của $z$, dẫn đến các chi trong tmp2
, như chúng tôi làm 3 x 9 = 27
. Lần này chúng ta cần cả hai chi/chữ số: tương đương với 7
đi tới tmp2[0]
và tương đương với 2
đi tới tmp2[1]
. Đây là một biến thể của thuật toán trường tiểu học: nó ngay lập tức được thêm vào tmp1
(tương đương giữa 1
Trong 116
) đến chi bậc thấp với add_uint64(tmp1, tmp2[0], &tmp1)
, thực hiện tương đương với 1 + 7 = 8
, không mang. Kết quả 8
được lưu trữ trong tmp1
bởi vì ngữ nghĩa của add_uint64
cần một điểm đến, nhưng nó thực sự bị bỏ qua, bởi vì chúng tôi không quan tâm đến chữ số ở giữa trong 986
. Sản lượng thực hiện bởi add_uint64
được sử dụng như toán hạng bên phải trong mang = tmp2[1] + â¦
, thực hiện tương đương với 1 + 0 = 1
trong ví dụ của chúng tôi. Mặc dù tên mang
, chứa chi/chữ số 64 bit đầy đủ.
Phép nhân thứ tư xảy ra trong z[1] * const_ratio_1
, nhân với chi bậc cao của $r$ với chi bậc cao của $z$, như chúng tôi làm 3 x 2 = 6
. Ở đây ngữ cảnh đảm bảo kết quả phù hợp với một chi duy nhất, do đó có thể sử dụng toán tử C riêng cho phép nhân. Kết quả sau đó được sử dụng làm toán tử bên trái của ⦠+ tmp3 + mang theo
, thực hiện tương đương với 6 + 1 + 1 = 8
. Một lần nữa, bối cảnh đảm bảo các giá trị này $\hat q$, được lưu trữ trong tmp1
, khớp với một nhánh/chữ số.
sau đó tmp3 = z[0] - tmp1 * modulus_value
thực hiện $\hat h:=z-q\cdot n$. Bối cảnh đảm bảo kết quả chính xác về mặt toán học phù hợp với một nhánh/chữ số (được lưu trữ trong tmp3
) mặc dù $q\cdot n$ không làm. Điều này cho phép sử dụng các toán tử C gốc, loại bỏ hoàn toàn việc tính toán chi bậc cao.
sau đó SEAL_COND_SELECT(tmp3 >= modulus_value, tmp3 - modulus_value, tmp3)
tính toán $h$ từ $\hat h$ trừ có điều kiện $n$ khi nào $\hat h\ge n$. Toán tử lựa chọn bị ẩn trong macro.
Hai ví dụ cho cơ sở $2^{64}$ (các giá trị ở dạng thập lục phân lớn có khoảng trắng được chèn giữa các chi):
mô-đun_value 000076513ae0b1cd
const_ratio 00000000000229e6 7f4ca82ba3a115f1
lấy<0>(I) 00005f0fd669f2c7
nhận được<1>(I) 000041a1f91ef16f
z 00000000185f2ae8 a455846cb7cf9b49
tmp1 000034bb854f9a8d
tmp3 00000fcebfd55b60
nhận được<2>(I) 00000fcebfd55b60
mô-đun_value 686f4b7702a9c775
const_ratio 0000000000000002 7387d66ffd685b82
lấy<0>(I) 536094611fa2b19b
nhận được<1>(I) 675ef5187093ff63
z 21aac8fcf31d6421 62e675ba16d513f1
tmp1 5287278703394bb1
tmp3 72b1d3d2b9f5e50c
nhận được<2>(I) 0a42885bb74c1d97
Lưu ý: đối với $n\in[2^{63}+1,\,2^{64}-1]$, số lượng $\hat h$ có thể tràn một chi và mã khi nó đứng không thành công. Ví dụ. cho đầu vào $f=g=2^{32}$, chúng tôi nhận được $z=2^{64}$ do đó $\hat q=0$ (bất cứ gì $n>2^{63}$), do đó $\hat h=z=2^{64}$ và một đầu ra của $0$ hơn là sự thật $h=2^{64}-n$. Nguồn đầy đủ có nhận xét "lớp Mô-đun đại diện cho mô-đun số nguyên không âm lên tới 61 bit", do đó, các vấn đề như vậy đối với quy mô lớn $n$ chỉ xảy ra khi mã gọi bị lỗi. Ngoài ra, nếu tôi hiểu đúng, $n=2^{60}-2^{14}+1$ là mục tiêu chính.
Thay thế: Đối với nội dung nào đó thực hiện chức năng giống như mã của câu hỏi, trong 4 dòng mã ngắn thay vì 11, cho tất cả $n\in[1,\,2^{64}-1]$, không cần tính toán trước, có thể nhanh hơn, nhưng chỉ tương thích với các trình biên dịch + CPU x64 gần đây, hãy xem phần đầu tiên của những đoạn mã này (thứ hai là một biến thể nhỏ không hạn chế $f\,g<2^{64}\,n$ ). Tôi không đưa ra tuyên bố nào về tính kịp thời của một trong hai mã.