Score:2

การลด Barret เพื่อให้ได้จำนวน 64 บิตที่เหลือจากจำนวน 128 บิต

ธง ru

บน GitHub มีสิ่งนี้ รหัสส่วนหนึ่งของ SEAL ของ Microsoft:

SEAL_ITERATE (iter (ตัวถูกดำเนินการ 1, ตัวถูกดำเนินการ 2, ผลลัพธ์), coeff_count, [&] (อัตโนมัติ I) {
    // ลด z โดยใช้ฐาน 2^64 การลดลงของ Barrett
    ไม่ได้ลงชื่อ long long z[2], tmp1, tmp2[2], tmp3, พกพา;
    multiply_uint64(รับ<0>(I), รับ<1>(I), z);

    // คูณอินพุตและ const_ratio
    // รอบที่ 1
    multiply_uint64_hw64(z[0], const_ratio_0, &พกพา);
    multiply_uint64(z[0], const_ratio_1, tmp2);
    tmp3 = tmp2[1] + add_uint64(tmp2[0], พกพา, &tmp1);

    // รอบที่ 2
    multiply_uint64(z[1], const_ratio_0, tmp2);
    พกพา = tmp2[1] + add_uint64(tmp1, tmp2[0], &tmp1);

    // นี่คือทั้งหมดที่เราสนใจ
    tmp1 = z[1] * const_ratio_1 + tmp3 + พกพา;

    // การลบบาร์เร็ตต์
    tmp3 = z[0] - tmp1 * โมดูลัส_ค่า;

    // อ้างสิทธิ์: ลบอีกครั้งก็พอ
    รับ<2>(I) = SEAL_COND_SELECT(tmp3 >= modulus_value, tmp3 - modulus_value, tmp3);
});

ที่ควรทำ การลดบาร์เร็ตต์เทคนิคการคำนวณค่าโมดูลัสโดยไม่หาร

ดูเหมือนว่า multiply_uint64_hw64 คูณตัวเลข 64 บิตสองตัวและรับเฉพาะ 64 บิตที่มีนัยสำคัญที่สุด multiply_uint64 รับหมายเลข 128 บิตในสองหมายเลข 64 บิตอย่างไรก็ตาม ฉันไม่เข้าใจว่ากำลังทำอะไรอยู่ และที่สำคัญที่สุดคือทำที่ไหน

$$a-\left\fชั้น a\,s\right\rfloor\,n$$

เกิดขึ้น. ไม่มีแม้แต่ฟังก์ชั่นพื้นในรหัสนี้

fgrieu avatar
ng flag
โค้ดคูณอินพุต `get(I)` และ `get(I)` เป็น `z` จากนั้นลดค่าคงที่โมดูโล `z` `modulus_value`$=n$ ด้วยผลลัพธ์ `get(I)` $s=1/n$ ในวิกิที่เชื่อมโยงเกี่ยวกับการลดขนาด Barrett ได้รับการปรับขนาดเป็น $r=\lfloor2^{128}/n\rfloor$ คำนวณล่วงหน้าจากภายนอกเป็น `const_ratio_0` และ `const_ratio_1` (ต่ำและสูง 64 บิต แขนขา). หากมีบางสิ่งที่ยังลึกลับอยู่ โปรดระบุมัน และควรถอดความสิ่งที่คุณเข้าใจ (รวมถึงคำใบ้ของฉัน) ในคำถาม โดยตั้งชื่อตัวแปรที่ดีและสอดคล้องกัน เช่น $r_0+2^{64}r_1=r$ สำหรับ `const_ratio` เหมือนกันกับ `z` และ `tmp2`
ru flag
@fgrieu โดยพื้นฐานแล้วฉันไม่เข้าใจการแยกออกเป็นบิตที่มีนัยสำคัญมากที่สุดและน้อยที่สุด การคูณทำงานกับส่วนบนและส่วนล่างได้อย่างไร ฉันเข้าใจว่า `const_ratio` ถูกแบ่งออกเป็น 2 ส่วนอย่างไร แต่ไม่เข้าใจวิธีการทำงานหลังจากนั้น
Maarten Bodewes avatar
in flag
บางทีฉันอาจง่ายเกินไปที่นี่ แต่ด้วยการดำเนินการจำนวนเต็มคุณไม่จำเป็นต้องใช้พื้น เนื่องจากการปัดเศษลงจะเหมือนกับการลืมทุกอย่างที่อยู่หลังเครื่องหมายจุลภาค
Score:3
ธง ng

รหัสของคำถามคำนวณ 64 บิต รับ<2>(I)=$h:=f\,g\bmod n$ จากอินพุต:

  • 64 บิต โมดูลัส_ค่า=$n$ กับ $n\in[2,\,2^{63}]$
  • 64 บิต รับ<0>(I)=$f$ กับ $f\in[0,\,2^{64}-1]$
  • 64 บิต รับ<1>(I)=$g$ กับ $g\in[0,\,2^{64}-1]$ และ $f\,g<2^{64}\,n$เงื่อนไขที่เป็นไปตามถ้า $f,g\in[0,\,n-1]$ (ซึ่งฉันคิดว่าเป็นกรณีนี้เสมอในแอปพลิเคชัน)

ผลลัพธ์ $h$ คือส่วนที่เหลือของการหารแบบยุคลิดของ $f\,ก$ โดย $n$. ถูกกำหนดโดยทางคณิตศาสตร์ $0\le ชั่วโมง<m$ และ $\exists q\in\mathbb Z,\ f\,g=q\cdot n+h$.

รหัสจะคำนวณ 128 บิตก่อน ซี$=z:=f\,g$แล้วผลลัพธ์ รับ<2>(I)$=h:=z\bmod n$ โดย การลดบาร์เร็ตต์:

  • มันถูกคำนวณล่วงหน้า (ภายนอกกับรหัสของคำถาม) const_ratio$=r=\left\lfloor2^{128}/n\right\rfloor$
  • $\hat q:=\left\lชั้น z\,r/2^{128}\right\rชั้น$ซึ่งถูกต้อง $คิว$ ภายในหนึ่งโดยค่าเริ่มต้น (หมายเหตุ: $\หมวก q$ เป็นค่าสุดท้ายของตัวแปร tmp1).
  • $\หมวก h:=z-q\cdot n$ซึ่งถูกต้อง $h$ ภายในอาจเกินพอดี $n$ (บันทึก: $\หมวก h$ เป็นค่าสุดท้ายของตัวแปร tmp3).
  • $h:=\หมวก h-n$ เมื่อไร $\หมวก h\ge n$, หรือ $h:=\หมวก h$ มิฉะนั้น.

รหัสนี้ใช้อัลกอริทึมของโรงเรียนประถมศึกษาในการคำนวณเลขหลายหลักโดยเปลี่ยนจากฐาน $10$ เพื่อฐาน $2^{64}$ (ด้วยการเพิ่มประสิทธิภาพและรายละเอียดปลีกย่อยในส่วนถัดไป) สิ่งที่เทียบเท่ากับตัวเลขเรียกว่า แขนขาที่นี่ 64 บิต

ผลิตภัณฑ์ ซี$=z$ แสดงเป็นสองแขนขา ซี[0]$=z_0$ (ลำดับต่ำ), ซี[1]$=z_1$ (ลำดับสูง) ด้วยประการฉะนี้ $z=z_0+2^{64}\,z_1$, และ $z_0, z_1\in[0,\,2^{64}-1]$.

ตัวคูณ Barrett const_ratio$=r=\left\lfloor2^{128}/n\right\rfloor$ แสดงออกมาในทำนองเดียวกันเป็นสองแขนขา const_ratio_0$=r_0$ และ const_ratio_1$=r_1$ขอบคุณค่าต่ำสุดของช่วงเวลาในเงื่อนไขเบื้องต้น $n\in[2,\,2^{63}]$.

ผลิตภัณฑ์ตัวกลาง $z\,r$ พอดีกับสามขาเสมอ (แม้ว่าโดยทั่วไปแล้วผลิตภัณฑ์ของปริมาณสองที่แสดงเป็นสองขาจะต้องใช้สี่ขา)

ผลหารเบื้องต้นโดยค่าเริ่มต้น $\หมวก q$ เหมาะกับแขนขาเดียวตั้งแต่ $\หมวก q\le q$ และ $q<2^{64}$โดยมีผู้ประกันตนรายล่าสุดตามเงื่อนไขเบื้องต้นในการป้อนข้อมูล $f\,g<2^{64}\,n$.

ส่วนที่เหลือเบื้องต้น $\หมวก h$ เหมาะกับแขนขาเดียวตั้งแต่ $\หมวก ชั่วโมง<2n$ และ $2n\le2^{64}$ตามมาด้วยจุดสิ้นสุดสูงของช่วงเวลาในเงื่อนไขเบื้องต้น $n\in[2,\,2^{63}]$.


รายละเอียดอัลกอริทึมของโค้ด (ตามที่ถามใน ความคิดเห็น และค่าหัว):

ฉันจะใช้ภาพประกอบเป็นทศนิยม ในฐานนั้นเป็นต้นมา $2\le n\le10/2$, const_ratio$=r$ สามารถเป็นได้ $\left\lfloor100/2\right\rfloor=50$, $\left\lfloor100/3\right\rfloor=33$, $\left\lfloor100/4\right\rfloor=25$, $\left\lfloor100/5\right\rfloor=20$แต่ฉันจะเสแสร้ง const_ratio$=r=29$ เพราะนั่นเป็นตัวอย่างที่น่าสนใจกว่า ด้วยเหตุผลเดียวกันฉันจะใช้ ซี$=z=34$แม้ว่าจะไม่ได้เป็นผลคูณของเลขสองหลักก็ตาม

ผลิตภัณฑ์ ซี ได้รับในรหัสโดย multiply_uint64(รับ<0>(I), รับ<1>(I), z) เป็นสองขา ซี[0] และ ซี[1].

เนื้อของการคำนวณคือ $\hat q:=\left\lชั้น z\,r/2^{128}\right\rชั้น$. นั่นคืออะนาล็อกในฐาน $2^{64}$ ของ $9:=\left\floor29\cdot34/100\right\rfloor$ ในฐาน 10 อาร์กิวเมนต์ทั้งสอง $29$ และ $34$ ในการคูณเป็นตัวเลขสองหลัก แต่มีขนาดเล็กพอที่ผลคูณของมัน $986$ เป็นตัวเลขสามหลัก (แทนที่จะเป็นสี่) และเราสนใจเฉพาะตัวเลขที่สามจากด้านขวาเท่านั้น อัลกอริทึมของโรงเรียนประถมศึกษาในการคำนวณ $986:=29\cdot34$ จะนำเสนอเป็น

      2 9 const_ratio
   x 3 4 ซ
    -----
    1 1 6
+ 8 7
  -------
    9 8 6

ในอัลกอริทึมของโรงเรียนประถมมีการคูณเลขหลักเดียวสี่ตัว (ซึ่งรหัสดำเนินการ) และการดำเนินการพิเศษบางอย่าง (ที่รหัสจัดระเบียบใหม่เล็กน้อย):

  • 4 ครั้ง 9, 36; เขียน 6, เก็บไว้ 3;
  • 4 ครั้ง 2, 8; บวก 3 (เก็บไว้), 11; เขียนว่า.
  • 3 ครั้ง 9, 27; เขียน 7, เก็บไว้ 2;
  • 3 ครั้ง 2, 6; บวก 2 (เก็บไว้), 8; เขียนว่า.

การคูณครั้งแรกในสี่ครั้งนี้เกิดขึ้นในโค้ดแฟรกเมนต์ multiply_uint64_hw64(z[0], const_ratio_0, &พกพา)ซึ่งคูณแขนขาลำดับต่ำของ $r$ ด้วยแขนขาลำดับต่ำของ $z$เหมือนเราคูณเลขลำดับต่ำ 4 ของ 34 ด้วยเลขลำดับต่ำ 9 ของ 29. สังเกตว่า "เขียน 6" ไม่มีประโยชน์ในกรณีนี้ เนื่องจากหลักใดก็ตามที่เขียนจะถูกแยกออกจากกันในคอลัมน์ด้านขวาของการคำนวณโดยไม่มีโอกาสที่จะส่งผลต่อหลักซ้ายสุด และจะไม่สนใจเมื่อเราหารด้วย 100 และปัดเศษลง (เท่ากับ เก็บเฉพาะหลักที่สามจาก ด้านขวา) นั่นเป็นสาเหตุที่ผลิตภัณฑ์ 128 บิตลำดับต่ำ 64 บิตไม่ได้รับการคำนวณด้วยซ้ำตามที่ระบุไว้ในคำถาม เทียบเท่ากับ 3 ใน 36 ถูกเก็บไว้ใน พก.

การคูณครั้งที่สองเกิดขึ้นใน multiply_uint64(z[0], const_ratio_1, tmp2)ซึ่งคูณแขนขาลำดับสูงของ $r$ ด้วยแขนขาลำดับต่ำของ $z$โดยมีผลให้แขนขาทั้งสองของ tmp2; 64 บิต ทีเอ็มพี[0] ได้รับเทียบเท่ากับ 8 ใน 8, และ ทีเอ็มพี[1] ได้รับเทียบเท่ากับ 0 สำหรับ (สังเกตนำหน้า 0 ถูกระงับในการเขียนเลขจำนวนเต็มฐานสิบแบบเดิม) เท่ากับ 8 บวก 3 เกิดขึ้นใน add_uint64(tmp2[0], พกพา, &tmp1)ด้วยตัวเลขลำดับต่ำ 1 ของผลลัพธ์ 11 ใน tmp1และพกพาใหม่ 1 ในเอาต์พุตของฟังก์ชันนั้น ที่ใช้เป็นตัวถูกดำเนินการใน tmp3 = tmp2[1] + ⦠(ซึ่งถูกข้ามไปในอัลกอริทึมของโรงเรียนประถมด้วยตัวอย่างเฉพาะที่ฉันเอามาตั้งแต่ 0 ถูกระงับ) ให้ผลเท่ากับฝ่ายซ้าย 1 ใน 116. [หมายเหตุเกี่ยวกับผลลัพธ์ของ add_uint64: มันถูกสร้างขึ้นโดย static_cast<ถ่านที่ไม่ได้ลงนาม>(ผลลัพธ์* <ตัวถูกดำเนินการ1)ซึ่งเปรียบเทียบ *ผลลัพธ์ และ ตัวถูกดำเนินการแล้วเลี้ยว จริง ถึง 1, เท็จ ถึง 0. ทำหลังจาก *ผลลัพธ์ = ตัวถูกดำเนินการ 1 + ตัวถูกดำเนินการ 2ที่บอกว่าการเพิ่มนี้ทำให้เกิดการพกพาหรือไม่ คอมไพเลอร์บางคนรู้จักสำนวนนี้ ใช้บิต C ของคำสถานะ และใช้ C ซ้ำในส่วนเพิ่มเติมที่กำลังจะมาถึง]

การคูณครั้งที่สามเกิดขึ้นใน multiply_uint64(z[1], const_ratio_0, tmp2)ซึ่งคูณแขนขาลำดับต่ำของ $r$ ด้วยกิ่งก้านสาขาระดับสูงของ $z$มีผลให้แขนขาใน tmp2เหมือนที่เราทำ 3 x 9 = 27. เวลานี้เราต้องการทั้งแขนขา/หลัก: เทียบเท่ากับ 7 ไปที่ tmp2[0] และเทียบเท่ากับ 2 ไปที่ tmp2[1]. นี่คืออัลกอริทึมของโรงเรียนประถมที่แตกต่างกัน: เพิ่มทันที tmp1 (เทียบเท่าของกลาง 1 ใน 116) ไปสู่กิ่งก้านลำดับต่ำด้วย add_uint64(tmp1, tmp2[0], &tmp1)มีประสิทธิภาพเทียบเท่ากับ 1 + 7 = 8,ไม่มีพกพา. ผลลัพธ์ 8 ถูกเก็บไว้ใน tmp1 เพราะความหมายของ add_uint64 ต้องการปลายทาง แต่จริงๆ ไม่สนใจ เพราะเราไม่สนใจหลักกลางใน 986. การส่งออกโดย add_uint64 ใช้เป็นตัวถูกดำเนินการใน พกพา = tmp2[1] + â¦มีประสิทธิภาพเทียบเท่ากับ 1 + 0 = 1 ในตัวอย่างของเรา แม้จะมีชื่อ พกที่เก็บแขนขา/หลัก 64 บิตเต็มรูปแบบ

การคูณที่สี่เกิดขึ้นใน z[1] * const_ratio_1ซึ่งคูณแขนขาลำดับสูงของ $r$ ด้วยกิ่งก้านสาขาระดับสูงของ $z$เหมือนที่เราทำ 3 x 2 = 6. ในที่นี้ บริบทรับประกันว่าผลลัพธ์จะพอดีกับแขนขาเดียว ดังนั้นจึงสามารถใช้โอเปอเรเตอร์ C ดั้งเดิมสำหรับการคูณได้ ผลลัพธ์จะถูกใช้เป็นตัวดำเนินการด้านซ้ายของ ⦠+ tmp3 + พกพามีประสิทธิภาพเทียบเท่ากับ 6 + 1 + 1 = 8. บริบทอีกครั้งรับประกันค่านี้ $\หมวก q$,เก็บไว้ใน tmp1พอดีกับแขนขา/หลักเดียว

แล้ว tmp3 = z[0] - tmp1 * โมดูลัส_ค่า ดำเนินการ $\หมวก h:=z-q\cdot n$. บริบทรับประกันว่าผลลัพธ์ที่แน่นอนทางคณิตศาสตร์จะพอดีกับแขนขา/หลักเดียว (เก็บไว้ใน tmp3) แม้ว่า $q\cdotn$ ไม่. สิ่งนี้ทำให้สามารถใช้ตัวดำเนินการเนทีฟ C ซึ่งข้ามการคำนวณส่วนที่มีลำดับสูงไปโดยสิ้นเชิง

แล้ว SEAL_COND_SELECT(tmp3 >= modulus_value, tmp3 - modulus_value, tmp3) คำนวณ $h$ จาก $\หมวก h$ โดยการลบอย่างมีเงื่อนไข $n$ เมื่อไร $\หมวก h\ge n$. ตัวดำเนินการการเลือกถูกซ่อนอยู่ในแมโคร

สองตัวอย่างสำหรับฐาน $2^{64}$ (ค่าเป็นเลขฐานสิบหกแบบ big-endian โดยมีช่องว่างระหว่างรยางค์):

โมดูลัส_ค่า 000076513ae0b1cd
const_ratio 00000000000229e6 7f4ca82ba3a115f1
รับ<0>(I) 00005f0fd669f2c7
รับ<1>(I) 000041a1f91ef16f
z 00000000185f2ae8 a455846cb7cf9b49
tmp1 000034bb854f9a8d
tmp3 00000fcebfd55b60
รับ<2>(I) 00000fcebfd55b60

โมดูลัส_ค่า 686f4b7702a9c775
const_ratio 0000000000000002 7387d66ffd685b82
รับ<0>(I) 536094611fa2b19b
รับ<1>(I) 675ef5187093ff63
z 21aac8fcf31d6421 62e675ba16d513f1
tmp1 5287278703394bb1
tmp3 72b1d3d2b9f5e50c
รับ<2>(I) 0a42885bb74c1d97

หมายเหตุ: สำหรับ $n\in[2^{63}+1,\,2^{64}-1]$, ปริมาณ $\หมวก h$ สามารถล้นแขนขาหนึ่งและรหัสในขณะที่มันล้มเหลว เช่น. สำหรับการป้อนข้อมูล $f=g=2^{32}$, เราได้รับ $z=2^{64}$ ดังนั้น $\หมวก q=0$ (สำหรับใดๆ $n>2^{63}$), ดังนั้น $\หมวก h=z=2^{64}$ และเอาต์พุตของ $0$ มากกว่าที่จะเป็นความจริง $h=2^{64}-n$. แหล่งที่มาแบบเต็มมีความคิดเห็น "คลาสโมดูลัสแสดงถึงโมดูลัสจำนวนเต็มที่ไม่ใช่ค่าลบสูงถึง 61 บิต" ดังนั้นปัญหาดังกล่าวสำหรับขนาดใหญ่ $n$ จะเกิดขึ้นเฉพาะเมื่อรหัสการโทรผิดพลาดเท่านั้น นอกจากนี้ ถ้าผมเข้าใจถูกต้อง $n=2^{60}-2^{14}+1$ เป็นเป้าหมายหลัก


ทางเลือก: สำหรับสิ่งที่ทำหน้าที่เดียวกันกับรหัสของคำถาม ในรหัสสั้นๆ 4 บรรทัดแทน 11 สำหรับทั้งหมด $n\in[1,\,2^{64}-1]$ไม่จำเป็นต้องมีการคำนวณล่วงหน้า อาจเร็วกว่า แต่เข้ากันได้กับคอมไพเลอร์ x64+CPU ล่าสุดเท่านั้น ดูส่วนแรกของ ข้อมูลโค้ดเหล่านี้ (อันที่สองคือตัวแปรขนาดเล็กที่ไม่มีข้อจำกัด $f\,g<2^{64}\,n$ ). ฉันไม่ได้แถลงเกี่ยวกับความคงที่ของรหัสทั้งสอง

ru flag
เหตุใด `add_uint64` จึงส่งคืน $0$ หรือ $1$ เสมอ ไม่มีความเป็นไปได้ในการพกพาที่ใหญ่กว่านี้หรือ?
fgrieu avatar
ng flag
@Guerlando OCs: `add_uint64` เพิ่มสองขาที่ผ่านเป็นอาร์กิวเมนต์ที่หนึ่งและสอง ดังนั้นสองจำนวนใน $[0,\ 2^{64}-1]$ ผลลัพธ์ที่แน่นอนทางคณิตศาสตร์จึงอยู่ใน $[0,\ 2^{65}-2]$ ดังนั้นจึงเหมาะกับขา 64 บิตหนึ่งตัวและหนึ่งบิต ซึ่งคล้ายกับผลบวกของทศนิยมสองหลักที่รวมกันเป็นหนึ่งหลักและมี 0 (เช่น 4+5=9) หรือ 1 (เช่น 9+9=18)

โพสต์คำตอบ

คนส่วนใหญ่ไม่เข้าใจว่าการถามคำถามมากมายจะปลดล็อกการเรียนรู้และปรับปรุงความสัมพันธ์ระหว่างบุคคล ตัวอย่างเช่น ในการศึกษาของ Alison แม้ว่าผู้คนจะจำได้อย่างแม่นยำว่ามีคำถามกี่ข้อที่ถูกถามในการสนทนา แต่พวกเขาไม่เข้าใจความเชื่อมโยงระหว่างคำถามและความชอบ จากการศึกษาทั้ง 4 เรื่องที่ผู้เข้าร่วมมีส่วนร่วมในการสนทนาด้วยตนเองหรืออ่านบันทึกการสนทนาของผู้อื่น ผู้คนมักไม่ตระหนักว่าการถามคำถามจะมีอิทธิพลหรือมีอิทธิพลต่อระดับมิตรภาพระหว่างผู้สนทนา