So sánh gấp đôi với 0 bằng cách sử dụng epsilon


214

Hôm nay, tôi đã xem qua một số mã C ++ (được viết bởi người khác) và tìm thấy phần này:

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

Tôi đang cố gắng tìm hiểu xem điều này thậm chí có ý nghĩa.

Các tài liệu cho epsilon()biết:

Hàm trả về chênh lệch giữa 1 và giá trị nhỏ nhất lớn hơn 1 có thể biểu thị [bằng một lần kép].

Điều này có áp dụng cho 0 không, tức epsilon()là giá trị nhỏ nhất lớn hơn 0? Hoặc có những con số giữa 00 + epsiloncó thể được đại diện bởi một double?

Nếu không, thì không phải là so sánh tương đương với someValue == 0.0?


3
Epsilon khoảng 1 rất có thể sẽ cao hơn nhiều so với khoảng 0, vì vậy có thể sẽ có các giá trị trong khoảng từ 0 đến 0 + epsilon_at_1. Tôi đoán tác giả của phần này muốn sử dụng một cái gì đó nhỏ, nhưng anh ta không muốn sử dụng hằng số ma thuật, vì vậy anh ta chỉ sử dụng giá trị cơ bản này tùy ý.
enobayram

2
So sánh số dấu phẩy động là khó khăn và việc sử dụng giá trị epsilon hoặc ngưỡng thậm chí còn được khuyến khích. Vui lòng tham khảo: cs.princeton.edu/introcs/91floatcygnus-software.com/auge/comparedfloats/comparedfloats.htmlm
Aditya Kumar Pandey

40
Liên kết đầu tiên là 403.99999999
graham.reeds

6
IMO, trong trường hợp này việc sử dụng numeric_limits<>::epsilonlà sai lệch và không liên quan. Điều chúng tôi muốn là giả sử 0 nếu giá trị thực tế khác nhau không quá một số ε từ 0. Và nên được chọn dựa trên đặc điểm kỹ thuật của vấn đề, chứ không phải dựa trên giá trị phụ thuộc vào máy. Tôi nghi ngờ rằng epsilon hiện tại là vô dụng, vì thậm chí chỉ một vài thao tác FP có thể tích lũy một lỗi lớn hơn thế.
Andrey Vihrov

1
+1. epsilon không phải là nhỏ nhất có thể nhưng có thể phục vụ mục đích nhất định trong hầu hết các nhiệm vụ kỹ thuật thực tế nếu bạn biết chính xác bạn cần gì và bạn đang làm gì.
SChepurin

Câu trả lời:


192

Giả sử nhân đôi 64 bit của IEEE, có một số mũ 52 bit và số mũ 11 bit. Hãy chia nó thành bit:

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

Số đại diện nhỏ nhất lớn hơn 1:

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

Vì thế:

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

Có bất kỳ số nào giữa 0 và epsilon? Rất nhiều ... Ví dụ: số đại diện tích cực (bình thường) tối thiểu là:

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

Trong thực tế, có những (1022 - 52 + 1)×2^52 = 4372995238176751616con số từ 0 đến epsilon, chiếm 47% của tất cả các số có thể biểu thị tích cực ...


27
Thật kỳ lạ khi bạn có thể nói "47% số dương" :)
cấu hình

13
@configurator: Không, bạn không thể nói rằng (không có biện pháp hữu hạn 'tự nhiên' nào tồn tại). Nhưng bạn có thể nói "47% số có thể biểu thị tích cực ".
Yakov Galka

1
@ybungalobill Tôi không thể tìm ra. Số mũ có 11 bit: 1 bit dấu và 10 bit giá trị. Tại sao 2 ^ -1022 mà không phải 2 ^ -1024 là số dương nhỏ nhất?
Pavlo Dyban

3
@PavloDyban: đơn giản vì số mũ không có bit dấu. Chúng được mã hóa dưới dạng offset: nếu số mũ được mã hóa là 0 <= e < 2048mantissa được nhân với 2 với sức mạnh của e - 1023. Ví dụ: số mũ của 2^0được mã hóa là e=1023, 2^1như e=10242^-1022như e=1. Giá trị của e=0được dành riêng cho subnormals và số không thực.
Yakov Galka

2
@PavloDyban: cũng 2^-1022là số bình thường nhỏ nhất . Số lượng nhỏ nhất là thực sự 0.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^-1022 = 2^-1074. Điều này là không bình thường, có nghĩa là phần mantissa nhỏ hơn 1, vì vậy nó được mã hóa theo số mũ e=0.
Yakov Galka

17

Bài kiểm tra chắc chắn không giống như someValue == 0. Toàn bộ ý tưởng của các số có dấu phẩy động là chúng lưu trữ số mũ và số có nghĩa. Do đó, chúng đại diện cho một giá trị với một số lượng chính xác nhị phân có ý nghĩa chính xác (53 trong trường hợp gấp đôi của IEEE). Các giá trị đại diện được đóng gói dày đặc hơn gần 0 so với gần 1.

Để sử dụng hệ thống thập phân quen thuộc hơn, giả sử bạn lưu trữ giá trị thập phân "đến 4 số liệu có ý nghĩa" với số mũ. Sau đó, giá trị đại diện tiếp theo lớn hơn 11.001 * 10^0, và epsilon1.000 * 10^-3. Nhưng 1.000 * 10^-4cũng có thể đại diện, giả sử rằng số mũ có thể lưu trữ -4. Bạn có thể tin tôi rằng một bộ đôi IEEE có thể lưu trữ số mũ ít hơn số mũ của epsilon.

Bạn không thể nói từ mã này một mình cho dù nó có ý nghĩa hay không sử dụng epsiloncụ thể như ràng buộc, bạn cần xem xét bối cảnh. Nó có thể epsilonlà một ước tính hợp lý về lỗi trong tính toán được tạo ra someValue, và có thể là nó không phải là.


2
Điểm tốt, nhưng ngay cả khi đó là trường hợp, một thực tiễn tốt hơn sẽ là giữ cho lỗi bị ràng buộc trong một biến có tên hợp lý và sử dụng nó trong so sánh. Khi nó đứng, nó không khác gì một hằng số ma thuật.
enobayram

Có lẽ tôi nên rõ ràng hơn trong câu hỏi của mình: Tôi không đặt câu hỏi liệu epsilon có phải là "ngưỡng" đủ lớn để bao gồm lỗi tính toán hay không nhưng liệu sự so sánh này có bằng someValue == 0.0hay không.
Sebastian Krysmanski

13

Có những số tồn tại giữa 0 và epsilon vì epsilon là sự khác biệt giữa 1 và số cao nhất tiếp theo có thể được biểu thị trên 1 và không phải là sự khác biệt giữa 0 và số cao nhất tiếp theo có thể được biểu thị trên 0 (nếu có, đó là mã sẽ làm rất ít): -

#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;
}

Sử dụng trình gỡ lỗi, dừng chương trình ở cuối chính và xem kết quả và bạn sẽ thấy epsilon / 2 khác với epsilon, zero và one.

Vì vậy, hàm này lấy các giá trị giữa +/- epsilon và làm cho chúng bằng không.


5

Một phép tính gần đúng của epsilon (chênh lệch nhỏ nhất có thể) xung quanh một số (1.0, 0.0, ...) có thể được in bằng chương trình sau. Nó in ra kết quả sau:
epsilon for 0.0 is 4.940656e-324
epsilon for 1.0 is 2.220446e-16
Một chút suy nghĩ cho thấy rõ ràng, rằng epsilon càng nhỏ thì số càng nhỏ chúng ta sử dụng để xem giá trị epsilon của nó, bởi vì số mũ có thể điều chỉnh theo kích thước của số đó.

#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
Những gì bạn đã kiểm tra? Đây chắc chắn không phải là trường hợp của GCC 4.7.
Anton Golov

3

Giả sử chúng ta đang làm việc với các số dấu phẩy động đồ chơi phù hợp với thanh ghi 16 bit. Có một bit dấu, số mũ 5 bit và mantissa 10 bit.

Giá trị của số dấu phẩy động này là mantissa, được hiểu là giá trị thập phân nhị phân, nhân hai lần với lũy thừa của số mũ.

Khoảng 1 số mũ bằng không. Vì vậy, chữ số nhỏ nhất của lớp phủ là một phần trong 1024.

Gần 1/2 số mũ là âm một, vì vậy phần nhỏ nhất của lớp phủ là một nửa. Với số mũ năm bit, nó có thể đạt tới âm 16, tại thời điểm đó, phần nhỏ nhất của lớp phủ có giá trị một phần trong 32m. Và ở số mũ âm 16, giá trị nằm trong khoảng một phần trong 32k, gần bằng 0 so với epsilon xung quanh một phần chúng tôi đã tính ở trên!

Bây giờ đây là mô hình điểm nổi đồ chơi không phản ánh tất cả các điểm kỳ quặc của hệ thống dấu phẩy động thực, nhưng khả năng phản ánh các giá trị nhỏ hơn epsilon tương đối hợp lý với các giá trị dấu phẩy động thực.


3

Sự khác biệt giữa Xvà giá trị tiếp theo Xkhác nhau tùy theo X.
epsilon()chỉ là sự khác biệt giữa 1và giá trị tiếp theo của 1.
Sự khác biệt giữa 0và giá trị tiếp theo 0là không epsilon().

Thay vào đó, bạn có thể sử dụng std::nextafterđể so sánh một giá trị kép 0như sau:

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

Tôi nghĩ rằng phụ thuộc vào độ chính xác của máy tính của bạn. Hãy nhìn vào bảng này : bạn có thể thấy rằng nếu epsilon của bạn được biểu thị bằng gấp đôi, nhưng độ chính xác của bạn cao hơn, so sánh không tương đương với

someValue == 0.0

Câu hỏi hay nào!


2

Bạn không thể áp dụng điều này cho 0, vì phần mantissa và số mũ. Do số mũ, bạn có thể lưu trữ rất ít số, nhỏ hơn epsilon, nhưng khi bạn cố gắng làm một cái gì đó như (1.0 - "số rất nhỏ") bạn sẽ nhận được 1.0. Epsilon là một chỉ số không phải là giá trị, mà là độ chính xác của giá trị, nằm ở lớp phủ. Nó cho thấy có bao nhiêu chữ số thập phân chính xác của số chúng ta có thể lưu trữ.


2

Với dấu phẩy động của IEEE, giữa giá trị dương khác không nhỏ nhất và giá trị âm khác không nhỏ nhất, tồn tại hai giá trị: 0 dương và 0 âm. Kiểm tra xem một giá trị có nằm giữa các giá trị khác không nhỏ nhất có tương đương với kiểm tra tính bằng không; việc chuyển nhượng, tuy nhiên, có thể có ảnh hưởng, vì nó sẽ thay đổi số 0 âm thành số 0 dương.

Có thể hình dung rằng một định dạng dấu phẩy động có thể có ba giá trị giữa các giá trị dương và âm hữu hạn nhỏ nhất: cực trị dương, không dấu và cực âm. Tôi không quen thuộc với bất kỳ định dạng dấu phẩy động nào thực tế hoạt động theo cách đó, nhưng hành vi như vậy sẽ hoàn toàn hợp lý và tốt hơn nhiều so với của IEEE (có lẽ không đủ tốt hơn để đáng thêm phần cứng bổ sung để hỗ trợ nó, nhưng về mặt toán học 1 / (1 / INF), 1 / (- 1 / INF) và 1 / (1-1) sẽ đại diện cho ba trường hợp riêng biệt minh họa ba số không khác nhau). Tôi không biết liệu có bất kỳ tiêu chuẩn C nào sẽ bắt buộc các infinitesimals đã ký, nếu chúng tồn tại, sẽ phải so sánh bằng không. Nếu họ không làm như vậy, mã như trên có thể đảm bảo rằng ví dụ


Không phải là "1 / (1-1)" (từ ví dụ của bạn) vô cùng chứ không phải không?
Sebastian Krysmanski

Các đại lượng (1-1), (1 / INF) và (-1 / INF) đều đại diện cho số 0, nhưng chia một số dương cho mỗi số trong lý thuyết sẽ mang lại ba kết quả khác nhau (toán học của IEEE coi hai kết quả đầu tiên giống hệt nhau ).
supercat

1

Vì vậy, giả sử hệ thống không thể phân biệt 1.000000000000000000000 và 1.000000000000000000001. đó là 1.0 và 1.0 + 1e-20. Bạn có nghĩ rằng vẫn còn một số giá trị có thể được biểu thị trong khoảng từ -1e-20 đến + 1e-20 không?


Ngoại trừ số 0, tôi không nghĩ rằng có các giá trị trong khoảng từ -1e-20 đến + 1e-20. Nhưng chỉ vì tôi nghĩ điều này không thành sự thật.
Sebastian Krysmanski

@SebastianKrysmanski: không đúng, có rất nhiều giá trị dấu phẩy động giữa 0 và epsilon. Bởi vì đó là điểm nổi chứ không phải điểm cố định.
Steve Jessop

Giá trị đại diện nhỏ nhất khác với 0 được giới hạn bởi số bit được phân bổ để biểu thị số mũ. Vì vậy, nếu double có số mũ 11 bit, số nhỏ nhất sẽ là 1e-1023.
cababunga

0

Ngoài ra, một lý do chính đáng để có chức năng như vậy là để loại bỏ "biến dạng" (những số rất nhỏ không còn có thể sử dụng hàm "1" ngụ ý và có biểu diễn FP đặc biệt). Tại sao bạn muốn làm điều này? Bởi vì một số máy (đặc biệt là một số Pentium 4s cũ) thực sự rất chậm khi xử lý biến dạng. Những người khác chỉ nhận được một chút chậm hơn. Nếu ứng dụng của bạn không thực sự cần những con số rất nhỏ này, thì việc xóa chúng về 0 là một giải pháp tốt. Những nơi tốt để xem xét đây là bước cuối cùng của bất kỳ bộ lọc IIR hoặc chức năng phân rã nào.

Xem thêm: Tại sao việc thay đổi 0,1f thành 0 làm chậm hiệu suất xuống 10 lần?

http://en.wikipedia.org/wiki/Den normal_number


1
Điều này loại bỏ nhiều số hơn so với chỉ số không chuẩn hóa. Nó thay đổi hằng số Planck hoặc khối lượng của electron thành 0 sẽ mang lại cho bạn kết quả rất, rất sai nếu bạn sử dụng những con số này.
gnasher729
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.