Epsilon kullanarak çift sıfıra karşılaştır


214

Bugün, (başka biri tarafından yazılmış) bazı C ++ kodu bakıyordu ve bu bölümü bulundu:

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

Bunun mantıklı olup olmadığını anlamaya çalışıyorum.

İçin belgeler epsilon()diyor:

İşlev 1 ile 1'den büyük olan [bir çiftle] temsil edilebilen en küçük değer arasındaki farkı döndürür.

Bu 0 için de geçerli mi, yani epsilon()en küçük değer 0'dan büyük mü? Yoksa arasında 0ve 0 + epsilonile temsil edilebilen sayılar var doublemı?

Değilse, karşılaştırma karşılaştırma ile eşdeğer değil someValue == 0.0mi?


3
1 civarındaki epsilon büyük olasılıkla 0'dan çok daha yüksek olacaktır, bu nedenle muhtemelen 0 ve 0 + epsilon_at_1 arasında değerler olacaktır. Sanırım bu bölümün yazarı küçük bir şey kullanmak istedi, ama sihirli bir sabit kullanmak istemedi, bu yüzden bu keyfi değeri kullandı.
enobayram

2
Kayan nokta sayılarını karşılaştırmak zordur ve epsilon veya eşik değeri kullanımı bile teşvik edilir. Lütfen bakınız: cs.princeton.edu/introcs/91float ve cygnus-software.com/papers/comparingfloats/comparingfloats.htm
Aditya Kumar Pandey

40
İlk bağlantı 403.99999999
graham.reeds

6
IMO, bu durumda numeric_limits<>::epsilonyanıltıcı ve önemsizdir. İstediğimiz şey, gerçek değer 0'dan 0'dan biraz daha fazla değilse 0 varsaymaktır. Ve ε, makineye bağlı bir değere göre değil, sorun spesifikasyonuna göre seçilmelidir. Şu anki epsilonun işe yaramaz olduğundan şüpheliyim, çünkü birkaç FP operasyonu bile bundan daha büyük bir hata biriktirebilir.
Andrey Vihrov

1
+1. epsilon mümkün olan en küçük değildir, ancak hangi hassasiyete ve neye ihtiyacınız olduğunu biliyorsanız pratik mühendislik görevlerinin çoğunda verilen amaca hizmet edebilir.
SChepurin

Yanıtlar:


192

64 bit IEEE çift olduğu varsayıldığında, 52 bit mantis ve 11 bit üs vardır. Bunu bitlere ayıralım:

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

Temsil edilebilir en küçük sayı 1'den büyük:

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

Bu nedenle:

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

0 ile epsilon arasında herhangi bir sayı var mı? Bolca ... Örn. Minimum pozitif temsil edilebilir (normal) sayı:

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

Aslında (1022 - 52 + 1)×2^52 = 43729952381767516160 ile epsilon arasında sayılar vardır , bu da temsil edilebilir tüm pozitif sayıların% 47'sidir ...


27
Öylesine garip diyebilirsin ki "pozitif sayıların% 47'si" :)
konfigüratör

13
@configurator: Hayır, söyleyemezsiniz ('doğal' sonlu önlem yoktur). Ama "Pozitif temsili sayıların % 47'si" diyebilirsiniz .
Yakov Galka

1
@ybungalobill Anlayamıyorum. Üssün 11 biti vardır: 1 işaret biti ve 10 değer biti. Neden en küçük pozitif sayı 2 ^ -1024 değil, 2 ^ -1022?
Pavlo Dyban

3
@ PavloDyban: sadece üslerin işaret biti olmadığı için. Ofsetler olarak kodlanırlar: eğer kodlanmış üs ise 0 <= e < 2048mantis 2 ile çarpılır e - 1023. Örneğinin üssü , olarak ve gibi 2^0kodlanır . Değeri alt normaller ve gerçek sıfır için ayrılmıştır. e=10232^1e=10242^-1022e=1e=0
Yakov Galka

2
@ PavloDyban: Ayrıca 2^-1022en küçük normal sayıdır. En küçük sayı aslında 0.0000 00000000 00000000 00000000 00000000 00000000 00000001 × 2^-1022 = 2^-1074. Bu alt normaldir, yani mantis kısmı 1'den küçüktür, bu nedenle üs ile kodlanır e=0.
Yakov Galka

17

Test kesinlikle aynı değildir someValue == 0. Kayan nokta sayıları fikri, bir üs ve önem depolamasıdır. Bu nedenle, belirli sayıda ikili anlamlı hassasiyet rakamına sahip bir değeri temsil ederler (IEEE çift durumunda 53). Temsili değerler 0'a yakındır ve 1'e yakındır.

Daha tanıdık bir ondalık sistem kullanmak için, üs ile birlikte "4 önemli rakama kadar" ondalık değer depoladığınızı varsayın. Daha sonra bir sonraki temsil edilebilir değer büyüktür 1olduğunu 1.001 * 10^0ve epsilonbir 1.000 * 10^-3. Ancak 1.000 * 10^-4, üssün -4'ü depolayabileceği varsayılarak da temsil edilebilir. IEEE iki katını Sen bunun için söz alabilir olabilir üs daha az üslerin saklamak epsilon.

Yalnızca bu koddan, epsilonözellikle sınır olarak kullanmanın anlamlı olup olmadığını anlayamazsınız , içeriğe bakmanız gerekir. O olabilir epsilonüretilen hesaplamada hata makul bir tahmindir someValueve bunun olmadığını olabilir.


2
İyi bir nokta, ancak bu durumda bile, hatayı makul bir şekilde adlandırılmış bir değişkente bağlı tutmak ve karşılaştırmada kullanmak daha iyi bir uygulamadır. Haliyle, sihirli bir sabitten farklı değildir.
enobayram

Belki de sorumda daha net olmalıydım: epsilon'un hesaplama hatasını kapsayacak kadar büyük bir "eşik" olup olmadığını değil, bu karşılaştırmanın eşit olup olmadığını sorgulamamıştım someValue == 0.0.
Sebastian Krysmanski

13

0 ile epsilon arasında sayılar vardır, çünkü epsilon, 1 ile 1'in üzerinde temsil edilebilecek bir sonraki en yüksek sayı arasındaki farktır ve 0 ile 0'ın üzerinde temsil edilebilecek bir sonraki en yüksek sayı arasındaki fark değildir (eğer öyleyse, kodu çok az yapardı): -

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

Bir hata ayıklayıcı kullanarak, programı ana sonunda durdurun ve sonuçlara bakın ve epsilon / 2'nin epsilon, sıfır ve bir'den farklı olduğunu göreceksiniz.

Böylece bu fonksiyon +/- epsilon arasındaki değerleri alır ve onları sıfır yapar.


5

Aşağıdaki programla bir sayı (1.0, 0.0, ...) etrafında epsilon (olası en küçük fark) yaklaştırılabilir. Aşağıdaki çıktıyı yazdırır:
epsilon for 0.0 is 4.940656e-324
epsilon for 1.0 is 2.220446e-16
Küçük bir düşünce, epsilonun küçüldüğünü netleştirir, epsilon değerine bakmak için kullandığımız sayı o kadar küçük olur, çünkü üs bu sayının boyutuna göre ayarlanabilir.

#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
Hangi uygulamaları kontrol ettiniz? Bu kesinlikle GCC 4.7 için geçerli değildir.
Anton Golov

3

16 bitlik bir kayıt defterine uyan oyuncak kayan nokta sayılarıyla çalıştığımızı varsayalım. Bir işaret biti, 5 bit üs ve 10 bit mantis var.

Bu kayan nokta sayısının değeri, üssün gücünün iki katı, ikili ondalık değer olarak yorumlanan mantistir.

1 civarında üs sıfıra eşittir. Yani mantisin en küçük rakamı 1024'te bir parçadır.

Üssün yaklaşık 1 / 2'si eksi birdir, bu nedenle mantisin en küçük kısmı yarı büyüktür. Beş bit üs ile negatif 16'ya ulaşabilir, bu noktada mantisin en küçük kısmı 32m'de bir kısma değer. Ve negatif 16 üssünde, değer 32k'de bir parça civarında, yukarıda hesapladığımız bir epsilondan sıfıra çok daha yakın!

Şimdi bu, gerçek bir kayan nokta sisteminin tüm tuhaflıklarını yansıtmayan bir oyuncak kayan nokta modeli, ancak epsilondan daha küçük değerleri yansıtma yeteneği, gerçek kayan nokta değerleri ile oldukça benzer.


3

Arasındaki fark Xve bir sonraki değer göre Xdeğişir X.
epsilon(), ile arasındaki bir 1sonraki değer arasındaki farktır 1.
İle 0sonraki değeri arasındaki fark 0değildir epsilon().

Bunun yerine, std::nextafterbir çift değeri 0aşağıdakilerle karşılaştırmak için kullanabilirsiniz :

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

Bunun bilgisayarınızın hassasiyetine bağlı olduğunu düşünüyorum . Bu tabloya bir göz atın : epsilon'unuz çiftle temsil ediliyorsa, ancak hassasiyetiniz daha yüksekse, karşılaştırmanın şuna eşdeğer olmadığını görebilirsiniz.

someValue == 0.0

Yine de iyi bir soru!


2

Mantis ve üs parçaları nedeniyle bunu 0'a uygulayamazsınız. Üs nedeniyle, epsilon'dan daha küçük olan çok küçük sayıları saklayabilirsiniz, ancak (1.0 - "çok küçük sayı") gibi bir şey yapmaya çalıştığınızda 1.0 alırsınız. Epsilon, değerin değil, mantis içindeki değer hassasiyetinin bir göstergesidir. Saklayabileceğimiz sayıların kaç tane doğru ondalık basamağını depolayabileceğimizi gösterir.


2

IEEE kayan nokta ile, en küçük sıfır olmayan pozitif değer ile en küçük sıfır olmayan negatif değer arasında iki değer vardır: pozitif sıfır ve negatif sıfır. Bir değerin sıfır olmayan en küçük değerler arasında olup olmadığını test etmek, sıfır ile eşitlik testine eşdeğerdir; bununla birlikte, atamanın bir etkisi olabilir, çünkü negatif bir sıfırı pozitif bir sıfıra değiştirir.

Kayan nokta biçiminin en küçük sonlu pozitif ve negatif değerler arasında üç değere sahip olabileceği düşünülebilir: pozitif sonsuz küçük, işaretsiz sıfır ve negatif sonsuz. Aslında bu şekilde çalışan herhangi bir kayan nokta biçimine aşina değilim, ama böyle bir davranış IEEE'ninkinden daha makul ve tartışmalı olarak daha iyi olurdu (belki de onu desteklemek için ekstra donanım eklemeye değecek kadar iyi değil, ama matematiksel olarak 1) / (1 / INF), 1 / (- 1 / INF) ve 1 / (1-1), üç farklı sıfır gösteren üç farklı durumu temsil etmelidir). Herhangi bir C standardının imzalanmış sonsuz sayıların, eğer varsa, sıfıra eşit karşılaştırması gerekip gerekmediğini bilmiyorum. Bunu yapmazlarsa, yukarıdaki gibi kodlar, örneğin;


"1 / (1-1)" (örneğinizden) sıfır yerine sonsuz değil mi?
Sebastian Krysmanski

(1-1), (1 / INF) ve (-1 / INF) miktarlarının hepsi sıfırı temsil eder, ancak pozitif bir sayıyı her birine bölmek teoride üç farklı sonuç vermelidir (IEEE matematiği ilk ikisini özdeş olarak görür ).
supercat

1

Diyelim ki sistem 1.000000000000000000000 ve 1.000000000000000000001'i ayırt edemez. 1.0 ve 1.0 + 1e-20. -1e-20 ve + 1e-20 arasında temsil edilebilecek bazı değerler olduğunu düşünüyor musunuz?


Sıfır dışında -1e-20 ve + 1e-20 arasında değerler olduğunu düşünmüyorum. Ama bunun bence doğru olmadığını düşünüyorum.
Sebastian Krysmanski

@SebastianKrysmanski: Bu doğru değil, 0 ile arasında çok sayıda kayan nokta değeri var epsilon. Çünkü kayan nokta, sabit nokta değil.
Steve Jessop

Sıfırdan farklı temsil edilebilen en küçük değer, üssü temsil etmek için ayrılan bit sayısı ile sınırlıdır. Eğer çiftin 11 bit üssü varsa, en küçük sayı 1e-1023 olur.
cababunga

0

Ayrıca, iyi bir neden böyle bir işleve sahip olmanın , "denormalleri" (zımni öncü "1" i kullanamayan ve özel bir FP temsiline sahip olmayan çok küçük sayılar) kaldırmaktır. Bunu neden yapmak istiyorsun? Çünkü bazı makineler (özellikle bazı eski Pentium 4s'ler) denormalleri işlerken gerçekten, gerçekten yavaşlar. Diğerleri biraz yavaşlar. Uygulamanız bu çok küçük sayılara gerçekten ihtiyaç duymuyorsa, sıfıra sıfırlamak iyi bir çözümdür. Bunu düşünmek için iyi yerler, herhangi bir IIR filtresinin veya bozunma fonksiyonunun son adımlarıdır.

Ayrıca bkz: 0.1f'yi 0'a değiştirmek neden performansı 10 kat yavaşlatıyor?

ve http://en.wikipedia.org/wiki/Denormal_number


1
Bu sadece denormalize sayılardan çok daha fazla sayı kaldırır. Planck sabitini veya bir elektron kütlesini sıfıra değiştirir, bu da bu sayıları kullanırsanız size çok, çok yanlış sonuçlar verecektir.
gnasher729
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.