เปรียบเทียบสองเท่ากับศูนย์โดยใช้ epsilon


214

วันนี้ฉันดูรหัส C ++ (เขียนโดยคนอื่น) และพบส่วนนี้:

double someValue = ...
if (someValue <  std::numeric_limits<double>::epsilon() && 
    someValue > -std::numeric_limits<double>::epsilon()) {
  someValue = 0.0;
}

ฉันพยายามที่จะคิดออกว่ามันทำให้รู้สึก

เอกสารสำหรับepsilon()พูดว่า:

ฟังก์ชันส่งคืนความแตกต่างระหว่าง 1 และค่าที่น้อยที่สุดที่มากกว่า 1 ที่สามารถแทนได้ [โดย double]

สิ่งนี้นำไปใช้กับ 0 เช่นกันนั่นepsilon()คือค่าที่เล็กที่สุดมากกว่า 0 หรือไม่ หรือมีตัวเลขระหว่าง0และ0 + epsilonที่สามารถแทนด้วยdouble?

หากไม่แสดงว่าการเปรียบเทียบนั้นไม่เทียบเท่าsomeValue == 0.0หรือไม่


3
epsilon ประมาณ 1 จะมีค่าสูงกว่ามากประมาณ 0 ดังนั้นอาจมีค่าระหว่าง 0 ถึง 0 + epsilon_at_1 ฉันเดาว่าผู้เขียนหัวข้อนี้ต้องการใช้สิ่งเล็ก ๆ แต่เขาไม่ต้องการใช้ค่าคงที่เวทย์มนตร์ดังนั้นเขาจึงใช้ค่านี้โดยพลการ
enobayram

2
การเปรียบเทียบจำนวนจุดลอยตัวนั้นเป็นเรื่องยากและยังสนับสนุนให้ใช้ epsilon หรือค่าเกณฑ์ โปรดอ้างอิง: cs.princeton.edu/introcs/91floatและcygnus-software.com/papers/comparingfloats/comparingfloats.htm
Aditya Kumar Pandey

40
ลิงก์แรกคือ 403.99999999
graham.reeds

6
IMO ในกรณีนี้การใช้งานnumeric_limits<>::epsilonจะทำให้เข้าใจผิดและไม่เกี่ยวข้อง สิ่งที่เราต้องการคือการสมมติว่า 0 หากค่าจริงแตกต่างกันไม่เกิน some จาก 0 และควรเลือกตามข้อกำหนดของปัญหาไม่ใช่ค่าที่ขึ้นกับเครื่อง ฉันสงสัยว่า epsilon ปัจจุบันนั้นไร้ประโยชน์เพราะแม้แต่การปฏิบัติการ FP เพียงเล็กน้อยก็สามารถสะสมข้อผิดพลาดได้มากกว่านั้น
Andrey Vihrov

1
+1 epsilon ไม่ใช่สิ่งที่เล็กที่สุดเท่าที่จะเป็นไปได้ แต่สามารถให้บริการตามวัตถุประสงค์ที่กำหนดไว้ในงานวิศวกรรมเชิงปฏิบัติส่วนใหญ่หากคุณรู้ว่าคุณต้องการความแม่นยำและสิ่งที่คุณกำลังทำอยู่
SChepurin

คำตอบ:


192

สมมติว่า IEEE 64- บิตเป็นสองเท่าจะมีแมนทิสซา 52- บิตและเลขชี้กำลัง 11 บิต ลองแบ่งเป็นบิต:

1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^0 = 1

จำนวนที่น้อยที่สุดที่สามารถแทนได้มากกว่า 1:

1.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^0 = 1 + 2^-52

ดังนั้น:

epsilon = (1 + 2^-52) - 1 = 2^-52

มีตัวเลขระหว่าง 0 ถึง epsilon หรือไม่ มากมาย ... เช่นจำนวนน้อยที่สุดที่สามารถแทนค่าบวก (ปกติ) คือ:

1.0000 00000000 00000000 00000000 00000000 00000000 00000000 × 2^-1022 = 2^-1022

ในความเป็นจริงมี(1022 - 52 + 1)×2^52 = 4372995238176751616ตัวเลขระหว่าง 0 ถึง epsilon ซึ่งเป็น 47% ของจำนวนที่สามารถแทนได้บวกทั้งหมด ...


27
แปลกมากที่คุณสามารถพูดว่า "47% ของจำนวนบวก" :)
กำหนดรูปแบบ

13
@configurator: ไม่คุณไม่สามารถพูดได้ (ไม่มีการวัดแน่นอน 'ที่มีอยู่จริง) แต่คุณสามารถพูดได้ว่า "47% ของจำนวนที่แทนได้บวก"
Yakov Galka

1
@ybungalobill ฉันไม่สามารถหามันได้ เลขชี้กำลังมี 11 บิต: บิตลงชื่อ 1 และบิต 10 ค่า ทำไม 2 ^ -1022 และไม่ใช่ 2 ^ -1024 เป็นจำนวนบวกที่เล็กที่สุด?
Pavlo Dyban

3
@PavloDyban: เพียงเพราะเลขชี้กำลังไม่มีเครื่องหมายบิต พวกเขาจะถูกเข้ารหัสเป็นชดเชย: ถ้าตัวแทนเข้ารหัสถูก0 <= e < 2048แล้ว mantissa คูณ 2 e - 1023ถึงอำนาจของ สัญลักษณ์เช่นของ2^0ถูกเข้ารหัสเป็นe=1023, 2^1เป็นe=1024และเป็น2^-1022 e=1ค่าของe=0ถูกสงวนไว้สำหรับ subnormals และศูนย์จริง
Yakov Galka

2
@PavloDyban: ยัง2^-1022เป็นหมายเลขปกติที่เล็กที่สุด 0.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^-1022 = 2^-1074จำนวนที่น้อยที่สุดเป็นจริง นี่คือต่ำกว่าปกติซึ่งหมายความว่าส่วนหนึ่ง mantissa มีขนาดเล็กกว่า 1 e=0จึงถูกเข้ารหัสด้วยเลขชี้กำลัง
Yakov Galka

17

การทดสอบไม่เหมือนกันอย่างsomeValue == 0แน่นอน แนวคิดทั้งหมดของตัวเลขทศนิยมคือพวกเขาเก็บเลขชี้กำลังและซิกนิฟิแคนด์ พวกเขาจึงเป็นตัวแทนของค่าที่มีจำนวนตัวเลขที่มีนัยสำคัญของความแม่นยำไบนารี (53 ในกรณีของ IEEE สองครั้ง) ค่าที่สามารถแทนค่าได้นั้นจะอยู่ใกล้กับค่าที่หนาแน่นกว่า 0 มากกว่าค่าที่อยู่ใกล้ 1

หากต้องการใช้ระบบทศนิยมที่คุ้นเคยมากขึ้นสมมติว่าคุณเก็บค่าทศนิยม "เป็นตัวเลขนัยสำคัญ 4 ถึง" พร้อมเลขชี้กำลัง แล้วค่าต่อไปมากขึ้นซึ่งแสดงกว่า1เป็น1.001 * 10^0และเป็นepsilon 1.000 * 10^-3แต่1.000 * 10^-4สามารถแทนได้ด้วยสมมติว่าเลขชี้กำลังสามารถเก็บ -4 ได้ คุณสามารถใช้คำของฉันมันว่า IEEE คู่สามารถepsilonเก็บเลขยกกำลังน้อยกว่าสัญลักษณ์ของ

คุณไม่สามารถบอกได้จากรหัสนี้เพียงอย่างเดียวไม่ว่าจะเหมาะสมหรือไม่ที่จะใช้epsilonโดยเฉพาะเป็นขอบเขตคุณจำเป็นต้องดูบริบท อาจเป็นได้ว่าepsilonมีการประเมินข้อผิดพลาดอย่างสมเหตุสมผลในการคำนวณที่เกิดขึ้นsomeValueและอาจเป็นได้ว่าไม่ใช่


2
จุดดี แต่ถึงแม้ว่าเป็นกรณีนี้แนวทางปฏิบัติที่ดีกว่าก็คือการเก็บข้อผิดพลาดไว้ในตัวแปรที่ตั้งชื่ออย่างสมเหตุสมผลและใช้มันในการเปรียบเทียบ มันไม่แตกต่างจากค่าคงที่เวทย์มนตร์
enobayram

บางทีฉันน่าจะชัดเจนกว่านี้ในคำถามของฉัน: ฉันไม่ได้ถามว่า epsilon นั้นเป็น "ขีด จำกัด " ที่ใหญ่พอที่จะครอบคลุมข้อผิดพลาดในการคำนวณ แต่ไม่ว่าการเปรียบเทียบนี้จะเท่ากับsomeValue == 0.0หรือไม่
เซบาสเตียน Krysmanski

13

มีตัวเลขที่อยู่ระหว่าง 0 ถึง epsilon เนื่องจาก epsilon คือความแตกต่างระหว่าง 1 และจำนวนสูงสุดถัดไปที่สามารถแทนด้านบน 1 และไม่ใช่ความแตกต่างระหว่าง 0 และจำนวนสูงสุดถัดไปที่สามารถแทนเหนือ 0 (ถ้าเป็นเช่นนั้น รหัสจะทำน้อยมาก): -

#include <limits>

int main ()
{
  struct Doubles
  {
      double one;
      double epsilon;
      double half_epsilon;
  } values;

  values.one = 1.0;
  values.epsilon = std::numeric_limits<double>::epsilon();
  values.half_epsilon = values.epsilon / 2.0;
}

เมื่อใช้ดีบักเกอร์ให้หยุดโปรแกรมที่จุดสิ้นสุดของ main และดูผลลัพธ์แล้วคุณจะเห็นว่า epsilon / 2 นั้นแตกต่างจาก epsilon, zero และ one

ฟังก์ชันนี้ใช้ค่าระหว่าง +/- epsilon และทำให้มันเป็นศูนย์


5

การประมาณของ epsilon (ความแตกต่างที่เล็กที่สุดที่เป็นไปได้) รอบ ๆ ตัวเลข (1.0, 0.0, ... ) สามารถพิมพ์ด้วยโปรแกรมต่อไปนี้ มันพิมพ์ผลลัพธ์ต่อไปนี้:
epsilon for 0.0 is 4.940656e-324
epsilon for 1.0 is 2.220446e-16
ความคิดเล็กน้อยทำให้ชัดเจนว่า epsilon จะเล็กลงยิ่งจำนวนที่เล็กลงนั้นคือจำนวนที่เราใช้เพื่อดูค่า epsilon เนื่องจากเลขชี้กำลังสามารถปรับให้มีขนาดของตัวเลขนั้นได้

#include <stdio.h>
#include <assert.h>
double getEps (double m) {
  double approx=1.0;
  double lastApprox=0.0;
  while (m+approx!=m) {
    lastApprox=approx;
    approx/=2.0;
  }
  assert (lastApprox!=0);
  return lastApprox;
}
int main () {
  printf ("epsilon for 0.0 is %e\n", getEps (0.0));
  printf ("epsilon for 1.0 is %e\n", getEps (1.0));
  return 0;
}

2
คุณได้ตรวจสอบการใช้งานอะไรบ้าง นี่ไม่ใช่กรณีของ GCC 4.7
Anton Golov

3

สมมติว่าเรากำลังทำงานกับของเล่นตัวเลขจุดลอยตัวที่พอดีกับการลงทะเบียน 16 บิต มีบิตเครื่องหมายเลขชี้กำลัง 5 บิตและ mantissa 10 บิต

ค่าของเลขทศนิยมนี้คือ mantissa ตีความว่าเป็นทศนิยมเลขฐานสองคูณสองด้วยกำลังของเลขชี้กำลัง

รอบ 1 เลขชี้กำลังเท่ากับศูนย์ ดังนั้นตัวเลขที่เล็กที่สุดของแมนทิสซาจึงเป็นส่วนหนึ่งใน 1024

ใกล้ 1/2 เลขชี้กำลังเป็นลบหนึ่งดังนั้นส่วนที่เล็กที่สุดของแมนทิสซาจึงมีขนาดครึ่งหนึ่ง ด้วยเลขชี้กำลังห้าบิตมันสามารถไปถึงค่าลบ 16 ซึ่งจุดที่ส่วนที่เล็กที่สุดของแมนทิสซานั้นมีค่าหนึ่งส่วนใน 32m และที่ลบเลขชี้กำลัง 16 ค่ามีค่าประมาณหนึ่งส่วนใน 32k ซึ่งใกล้เคียงกับศูนย์มากกว่าเอปไซลอนประมาณหนึ่งที่เราคำนวณข้างต้น!

ตอนนี้นี่เป็นโมเดลของเล่นจุดลอยตัวที่ไม่ได้สะท้อนถึงองค์ประกอบทั้งหมดของระบบจุดลอยตัวที่แท้จริง แต่ความสามารถในการสะท้อนค่าที่เล็กกว่าเอปไซลอนนั้นมีความคล้ายคลึงกับค่าจุดลอยตัวที่แท้จริง


3

ความแตกต่างระหว่างXและมูลค่าต่อไปของการแตกต่างกันไปตามX เป็นเพียงความแตกต่างระหว่างและมูลค่าต่อไปของ ความแตกต่างระหว่างและมูลค่าต่อไปของการไม่ได้X
epsilon()11
00epsilon()

คุณสามารถใช้std::nextafterเพื่อเปรียบเทียบค่าสองเท่าด้วย0ต่อไปนี้:

bool same(double a, double b)
{
  return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
    && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}

double someValue = ...
if (same (someValue, 0.0)) {
  someValue = 0.0;
}

2

ฉันคิดว่ามันขึ้นอยู่กับความแม่นยำของคอมพิวเตอร์ของคุณ ลองดูในตารางนี้: คุณจะเห็นว่าถ้า epsilon ของคุณแสดงเป็นสองเท่า แต่ความแม่นยำของคุณสูงกว่าการเปรียบเทียบนั้นไม่เทียบเท่า

someValue == 0.0

คำถามที่ดีอยู่แล้ว!


2

คุณไม่สามารถใช้สิ่งนี้กับ 0 เนื่องจากส่วนแมนทิสซาและเลขชี้กำลัง เนื่องจากเลขชี้กำลังคุณสามารถเก็บตัวเลขได้น้อยมากซึ่งเล็กกว่า epsilon แต่เมื่อคุณพยายามทำบางสิ่งเช่น (1.0 - "จำนวนน้อยมาก") คุณจะได้รับ 1.0 Epsilon เป็นตัวบ่งชี้ไม่ใช่ค่า แต่เป็นค่าความแม่นยำซึ่งอยู่ในแมนทิสซา มันแสดงจำนวนทศนิยมที่ตามมาของจำนวนที่เราสามารถจัดเก็บได้ถูกต้อง


2

ด้วย IEEE floating-point ระหว่างค่าบวกที่ไม่ใช่ศูนย์ที่เล็กที่สุดและค่าลบที่ไม่ใช่ศูนย์ที่เล็กที่สุดมีอยู่สองค่า: ศูนย์บวกและศูนย์ลบ การทดสอบว่าค่าอยู่ระหว่างค่าที่ไม่เป็นศูนย์เล็กที่สุดหรือเทียบเท่ากับการทดสอบเพื่อความเท่าเทียมกับศูนย์; อย่างไรก็ตามการมอบหมายอาจมีผลกระทบเนื่องจากจะเปลี่ยนศูนย์ลบเป็นศูนย์บวก

มันจะเป็นไปได้ว่ารูปแบบจุดลอยตัวอาจมีสามค่าระหว่างค่าบวกและลบที่มีค่าน้อยที่สุด: บวกเล็ก, ศูนย์ที่ไม่ได้ลงชื่อ, และลบน้อยที่สุด ฉันไม่คุ้นเคยกับรูปแบบทศนิยมใด ๆ ที่จริง ๆ แล้วทำงานแบบนั้น แต่พฤติกรรมดังกล่าวจะเหมาะสมอย่างสมบูรณ์และดีกว่า IEEE (อาจจะไม่ดีกว่าที่จะคุ้มค่าที่จะเพิ่มฮาร์ดแวร์เพิ่มเติมเพื่อรองรับ แต่ในทางคณิตศาสตร์ 1 / (1 / INF), 1 / (- 1 / INF), และ 1 / (1-1) ควรเป็นตัวแทนของสามกรณีที่แตกต่างกันซึ่งแสดงถึงศูนย์ที่แตกต่างกันสามแห่ง) ฉันไม่ทราบว่ามาตรฐาน C ใด ๆ ที่จะมอบอำนาจให้ infinitesimals ที่ลงนามแล้วถ้าพวกเขามีอยู่จะต้องเปรียบเทียบเท่ากับศูนย์ หากไม่มีรหัสดังกล่าวจะช่วยให้มั่นใจได้ว่าเป็นเช่นนั้น


ไม่ใช่ "1 / (1-1)" (จากตัวอย่างของคุณ) ไม่ใช่ศูนย์ใช่หรือไม่
เซบาสเตียน Krysmanski

ปริมาณ (1-1), (1 / INF) และ (-1 / INF) ทั้งหมดเป็นศูนย์ แต่การหารจำนวนบวกโดยแต่ละอันควรในทางทฤษฎีให้ผลลัพธ์ที่แตกต่างกันสามแบบ (คณิตศาสตร์ IEEE คำนึงถึงสองคนแรกเหมือนกัน )
supercat

1

สมมุติว่าระบบไม่สามารถแยกแยะความแตกต่าง 1.000000000000000000000 และ 1.000000000000000000001 นั่นคือ 1.0 และ 1.0 + 1e-20 คุณคิดว่ายังมีค่าบางอย่างที่สามารถแสดงระหว่าง -1e-20 และ + 1e-20 ได้หรือไม่


ยกเว้นศูนย์ฉันไม่คิดว่ามีค่าระหว่าง -1e-20 และ + 1e-20 แต่เพียงเพราะฉันคิดว่านี่ไม่ได้ทำให้เป็นจริง
เซบาสเตียน Krysmanski

@SebastianKrysmanski: มันไม่จริงมีจำนวนมากของค่าจุดลอยตัวระหว่าง 0 epsilonและ เพราะมันคือจุดลอยไม่ใช่จุดคงที่
Steve Jessop

ค่าที่สามารถแทนค่าได้น้อยที่สุดที่แตกต่างจากศูนย์จะถูก จำกัด ด้วยจำนวนบิตที่จัดสรรเพื่อแสดงถึงเลขชี้กำลัง ดังนั้นถ้า double มีเลขชี้กำลัง 11 บิตจำนวนที่เล็กที่สุดจะเป็น 1e-1023
cababunga

0

นอกจากนี้เหตุผลที่ดีสำหรับการใช้งานฟังก์ชั่นนี้คือการลบ "denormals" (จำนวนน้อยมากที่ไม่สามารถใช้ "1" นำหน้าโดยนัยและมีการแสดง FP พิเศษ) ทำไมคุณต้องการทำเช่นนี้? เนื่องจากเครื่องจักรบางเครื่อง (โดยเฉพาะ Pentium 4s รุ่นเก่า) ได้ช้าลงจริง ๆ เมื่อประมวลผล denormals คนอื่น ๆ เริ่มช้าลงบ้าง หากแอปพลิเคชันของคุณไม่ต้องการตัวเลขที่น้อยมากเหล่านี้การล้างให้เหลือศูนย์นั้นเป็นทางออกที่ดี สถานที่ที่ดีในการพิจารณานี่เป็นขั้นตอนสุดท้ายของตัวกรอง IIR หรือฟังก์ชันการสลายตัว

ดูเพิ่มเติม: ทำไมการเปลี่ยน 0.1f เป็น 0 จะทำให้ประสิทธิภาพลดลง 10 เท่า?

และhttp://en.wikipedia.org/wiki/Denormal_number


1
สิ่งนี้จะลบตัวเลขจำนวนมากออกไปมากกว่าแค่ตัวเลข มันเปลี่ยนค่าคงที่ของพลังค์หรือมวลของอิเล็กตรอนเป็นศูนย์ซึ่งจะให้ผลลัพธ์ที่ผิดพลาดมากถ้าคุณใช้ตัวเลขเหล่านี้
gnasher729
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.