Şamandıra sayıları eşitliği karşılaştırmak, benim durumumda yuvarlama hatası olmasa bile küçük geliştiricileri yanlış yönlendiriyor mu?


31

Örneğin, her 0.5 için atlayan 0,0.5, ... 5 düğmelerinden oluşan bir liste göstermek istiyorum. Bunu yapmak için for döngüsünü kullanıyorum ve STANDARD_LINE düğmesinde farklı renklerim var:

var MAX=5.0;
var DIFF=0.5
var STANDARD_LINE=1.5;

for(var i=0;i<=MAX;i=i+DIFF){
    button.text=i+'';
    if(i==STANDARD_LINE){
      button.color='red';
    }
}

Bu durumda, IEEE 754'te her değer tam olduğu için hiçbir yuvarlama hatası olmamalıdır.

var MAX=10;
var STANDARD_LINE=3;

for(var i=0;i<=MAX;i++){
    button.text=i/2.0+'';
    if(i==STANDARD_LINE/2.0){
      button.color='red';
    }
}

Bir yandan, orijinal kod daha basit ve bana iletiliyor. Ancak düşündüğüm bir şey var: i == STANDARD_LINE genç takım arkadaşlarını yanıltıyor mu? Kayan nokta sayılarının yuvarlama hataları olabileceği gerçeğini gizliyor mu? Bu gönderiden gelen yorumları okuduktan sonra:

https://stackoverflow.com/questions/33646148/is-hardcode-float-precise-if-it-can-be-represented-by-binary-format-in-ieee-754

Görünüşe göre birçok geliştirici, bazı şamandıra numaralarının kesin olduğunu bilmiyor. Benim durumumda geçerli olsa bile değişken sayı eşitlik karşılaştırmalarından kaçınmalı mıyım? Yoksa bu konuda mı düşünüyorum?


23
Bu iki kod listesinin davranışı eşdeğer değildir. 3 / 2.0 1.5, ancak iikinci listedeki sadece tam sayı olacak. İkincisi çıkarmayı deneyin /2.0.
candied_orange

27
İki FP'yi eşitlik için kesinlikle karşılaştırmanız gerekiyorsa (başkalarının tam cevaplarıyla bir döngü sayacı karşılaştırması kullanabildiğiniz için iyi cevaplarında belirtildiği gibi gerekli değildir), ancak yaptıysanız, yorum yeterli olacaktır. Şahsen uzun süredir IEEE FP ile çalışıyorum ve SPFP karşılaştırmasını herhangi bir yorum veya başka bir şey olmadan doğrudan gördüm, kafam karışır. Bu sadece çok hassas bir kod - IMHO en azından her seferinde bir yorum yapmaya değer.

14
Hangisini seçerseniz seçin, bu nasıl ve neden kesinlikle gerekli olduğunu açıklayan bir yorumun bulunduğu durumlardan biridir. Daha sonraki bir geliştirici, inceliklerini dikkatlerini üzerine çekecek bir yorum yapmadan bile düşünemeyebilir. Ayrıca, döngünüzün buttonhiçbir yerinde değişmemesinden dolayı çok dikkatim dağılıyor . Düğme listesine nasıl erişilir? Dizin üzerinden diziye mi yoksa başka bir mekanizmaya mı? Bir diziye indeks erişimiyle yapılıyorsa, bu tamsayılara geçmek için başka bir argümandır.
jpmc26

9
Bu kodu yaz. Birisi 0.6'nın daha iyi bir adım büyüklüğü olacağını düşünene kadar ve sadece o sabiti değiştirir.
tofro

11
“... genç geliştiricileri yanlış yönlendirin” Ayrıca üst düzey geliştiricileri de yanlış yönlendireceksiniz. İçine koyduğun düşünce miktarına rağmen, ne yaptığını bilmediğini ve muhtemelen yine de tamsayı sürümüne değiştireceğini varsayacaklar.
GrandOpener

Yanıtlar:


116

Bilgisayarım için gerekli olan model gerektirmedikçe, sürekli kayan nokta işlemlerinden kaçınırdım. Kayan nokta aritmetiği, çoğu ve büyük bir hata kaynağı için sezgisel değildir. Ve olmadığı durumlarda hatalara neden olduğu durumları söylemek, daha da ince bir ayrımdır!

Bu nedenle, float'ları loop sayaçları olarak kullanmak, gerçekleşmeyi bekleyen bir kusurdur ve en azından en azından burada 0,5 kullanmanın neden uygun olduğunu ve bunun belirli sayısal değere bağlı olduğunu açıklayan bir yorum gerektirir. Bu noktada, yüzdürme sayaçlarını önlemek için kodu yeniden yazmak muhtemelen daha okunaklı seçenek olacaktır. Okunabilirlik, profesyonel gereksinimler hiyerarşisindeki doğruluğun yanındadır.


48
"Olmasını bekleyen bir kusur" yu severim. Tabii ki, şimdi işe yarayabilir , ancak yürüyen birinden hafif bir esinti onu kıracak.
AakashM

10
Örneğin, gereksinimlerin değiştiğini varsayalım; böylece dördüncü düğmedeki "standart çizgi" ile 0'dan 5'e eşit aralıklı 11 tuş yerine, 6.daki "standart çizgiyle" 16 - 0 eşit aralıklı tuşa sahip olmalısınız buton. Öyleyse, bu kodu sizden alan kişi, 0,5 - 1,0 / 3,0 ve 1,5 - 5,0 / 3,0 arasında değişir. O zaman ne olacak?
David K,

8
Evet, ne değişen düşüncesi ile rahatsızım görünüyor keyfi bir sayı olmak başka keyfi numaraya (bir sayı olabileceği kadar "normal" gibi) (yani görünüyor aslında bir kusur tanıtır eşit "normal").
Alexander - Monica

7
@Alexander: tamam, söylenen bir yoruma ihtiyacınız olacak DIFF must be an exactly-representable double that evenly divides STANDARD_LINE. Bu yorumu yazmak istemiyorsanız (ve gelecekteki tüm geliştiricilere IEEE754 binary64 kayan nokta hakkında yeterince bilgi edinmesine güveniyorsanız), kodu bu şekilde yazmayın. yani, kodu bu şekilde yazmayın. Özellikle de muhtemelen daha verimli olmadığı için: FP ilavesi, tamsayı ilavesinden daha fazla gecikme süresi vardır ve döngüsel bir bağımlılıktır. Ayrıca, derleyiciler (hatta JIT derleyicileri?) Büyük olasılıkla tamsayı sayıcılarla döngüler oluşturmada muhtemelen daha iyidir.
Peter Cordes

39

Genel bir kural olarak, döngüler n kez bir şey yapmayı düşünecek şekilde yazılmalıdır . Kayan nokta indeksleri kullanıyorsanız, artık n kez bir şey yapmak değil, bir koşul yerine getirilinceye kadar koşmak artık bir soru değil. Bu koşul i<n, pek çok programcının beklediğine çok benziyorsa , kod gerçekten bir başka şeyi yaparken kodunu inceleyen programcılar tarafından kolayca yanlış yorumlanabilecek bir şey yapıyor gibi görünmektedir.

Bu biraz özneldir, ancak alçakgönüllü görüşüme göre, belirli bir sayıyı döngülemek için bir tamsayı dizini kullanmak için bir döngüyü yeniden yazdırabilirseniz, bunu yapmalısınız. Bu yüzden aşağıdaki alternatifi düşünün:

var DIFF=0.5;                           // pixel increment
var MAX=Math.floor(5.0/DIFF);           // 5.0 is max pixel width
var STANDARD_LINE=Math.floor(1.5/DIFF); // 1.5 is pixel width

for(var i=0;i<=MAX;i++){
    button.text=(i*DIFF)+'';
    if(i==STANDARD_LINE){
      button.color='red';
    }
}

Döngü tam sayılarla çalışır. Bu durumda ibir tamsayıdır ve bir tamsayıya STANDARD_LINEda zorlanır. Bu, elbette ki gidişat ve benzer şekilde gerçekleşmesi durumunda standart hattınızın konumunu değiştirir MAX, bu nedenle hala doğru renderleme için gidiş kaybını önlemek için çaba göstermelisiniz. Bununla birlikte, kayan noktaların karşılaştırılması konusunda endişelenmenize gerek kalmadan, tam sayı olarak değil, piksel cinsinden parametreleri değiştirme avantajına sahipsiniz.


3
İstediğinize bağlı olarak ödevlerde döşeme yerine yuvarlama düşünebilirsiniz. Bölünmenin bir tamsayı sonucu vermesi gerekiyorsa, bölmenin biraz uzakta olduğu sayılarla karşılaşırsanız zeminde sürprizler olabilir.
ilkkachu

1
@ilkkachu Doğru. Düşüncelerim şuydu: 5.0'ı maksimum piksel miktarı olarak ayarlıyorsanız, yuvarlamadan sonra, tercihen biraz daha fazla olmaktansa, 5.0'ın alt tarafında olmak istersiniz. 5.0 etkili bir şekilde maksimum olabilir. Yapmanız gerekenlere göre yuvarlama tercih edilebilir olsa da. Her iki durumda da, bölüm zaten bir tam sayı oluşturuyorsa, çok az fark yaratır.
Neil

4
Kesinlikle katılmıyorum. Bir döngüyü durdurmanın en iyi yolu, iş mantığını en doğal şekilde ifade eden şarttır. İş mantığı 11 düğüme gereksiniminiz varsa, döngü yineleme 11'de durmalıdır. İş mantığı, düğmelerin çizgi doluncaya kadar 0,5 ayrı olması durumunda, çizgi dolunca döngü durmalıdır. Seçimi bir mekanizmaya veya diğerine doğru itebilecek başka hususlar da var, ancak bu hususlar mevcut değilse, iş gereksinimine en uygun mekanizmayı seçin.
Monica

Açıklamanız Java için tamamen doğru olacaktır / C ++ / Yakut / Python / ... Ama JavaScript tamsayılar yoktur, bu yüzden ive STANDARD_LINEsadece tamsayılar benziyor. Hiçbir zorlama hiç, ve DIFF, MAXve STANDARD_LINEhepsi sadece vardır Numberbu. NumberTamsayı olarak kullanılanlar aşağıda güvenli olmalıdır 2**53, yine de kayan nokta sayılarıdır.
Eric Duminil

@EricDuminil Evet, ancak bunun yarısı. Diğer yarısı okunabilirlik. Optimizasyon için değil, bu şekilde yapmanın temel nedeni olarak bahsediyorum.
Neil

20

Tamsayı olmayan bir döngü değişkeni kullanmanın, bunun doğru çalışacağı gibi durumlarda bile genellikle kötü stilde olduğu diğer tüm cevaplara katılıyorum. Ama bana öyle geliyor ki burada neden kötü bir tarz olduğu bunun başka bir nedeni.

Kodunuz, mevcut satır genişliklerinin 0 ile 5,0 arasında 0,5'in katları olduğunu "bilir". Olmalı mı? Kolayca değişebilecek bir kullanıcı arayüzü kararı gibi görünüyor (örneğin, belki genişlikler arasındaki boşlukların genişlikler arttıkça daha büyük olmasını istersiniz. 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 2.5, 3.0, 4.0, 5.0 ya da bir şey).

Kodunuz, mevcut çizgi genişliklerinin hepsinin hem kayan noktalı sayılar hem de ondalık sayılar olarak "güzel" gösterimlerine sahip olduğunu "bilir". Bu da değişebilecek bir şeye benziyor. (Bir noktada 0,1, 0,2, 0,3, ... isteyebilirsiniz.)

Kodunuz, düğmelere basılacak metnin, Javascript’in kayan nokta değerlerini dönüştürdüğü şey olduğunu “bilir”. Bu da değişebilecek bir şeye benziyor. (Örneğin, belki bir gün 1/3 gibi genişlikler isteyeceksiniz, ki bu muhtemelen 0,3333333333333 veya her neyse göstermek istemezsiniz. Belki de "1,5" ile tutarlılık için "1" yerine "1.0" görmek istersiniz). .)

Bunların hepsi bana, bir çeşit katmanın karışması olan tek bir zayıflığın tezahürleri gibi geliyor. Bu kayan nokta sayıları, yazılımın dahili mantığının bir parçasıdır. Düğmelerde gösterilen metin, kullanıcı arayüzünün bir parçasıdır. Buradaki koddakilerden daha ayrı olmalıdırlar. "Bunlardan hangisi vurgulanmalıdır?" kullanıcı arabirimi konularıdır ve muhtemelen bu kayan nokta değerlerine bağlı olmamalıdır. Ve buradaki döngünüz gerçekten (ya da en azından olmalıdır) , çizgi genişliği üzerinden değil, düğmelerin üzerinden bir döngüdür . Bu şekilde yazıldığında, tamsayı olmayan değerleri alarak bir döngü değişkeni kullanma eğilimi kaybolur: sadece art arda tamsayılar veya for ... in / for ... for döngüsünü kullanırsınız.

Benim düşüncem, birisinin tamsayı olmayan sayılar üzerinden dolaşmak için cazip olabileceği çoğu durum şöyledir: Tamamen sayısal konularla ilgisi olmayan, kodun neden farklı düzenlenmesi gerektiğinin başka nedenleri de var. ( Her durumda değil ; bazı matematiksel algoritmaların en çok tam sayı olmayan değerler üzerinde bir döngü olarak ifade edilebileceğini hayal edebiliyorum.)


8

Bir kod kokusu, böyle bir döngü içinde yüzdürme kullanıyor

Döngü birçok şekilde yapılabilir, ancak vakaların% 99.9'unda 1'lik bir artışa sadık kalmanız gerekir, aksi takdirde sadece küçük geliştiriciler tarafından değil, karışıklık olacaktır.


Ben katılmıyorum, bence 1'in tam sayılarının bir for-loop içinde kafa karıştırıcı olmadığını düşünüyorum. Bir kod kokusu olduğunu düşünmezdim. Sadece kesirler.
CodeMonkey

3

Evet, bundan kaçınmak istiyorsun.

Kayan nokta sayıları, şüphesiz programcı için en büyük tuzaklardan biridir (bu, tecrübelerime göre neredeyse herkes anlamına gelir). Kayan nokta eşitlik testlerine bağlı olarak, paranın kayan nokta olarak gösterilmesine kadar, hepsi büyük bir bataklık. Birini bir yüzeye diğerine eklemek en büyük suçlulardan biridir. Böyle şeylerle ilgili çok sayıda bilimsel literatür var.

Kayan nokta sayılarını tam olarak uygun oldukları yerlerde, örneğin ihtiyaç duyduğunuz yerde gerçek matematiksel hesaplamalar yaparken (trigonometri, grafik grafikleri çizme vb.) Kullanın ve seri işlemleri yaparken çok dikkatli olun. Eşitlik doğru değildir. Hangi belirli sayı kümelerinin IEEE standartları ile kesin olduğu hakkında bilgi çok gizlidir ve asla buna güvenmeyeceğim.

Senin durumunda, orada olacak , Murphys Kanunu ile, yönetimi, 0.0, 0.5, 1.0 ... ama 0.0, 0.4, 0.8 ... ya da her neyse birşey istiyor noktasını gelip; derhal sinirleneceksin ve küçük programcınız (veya kendiniz) sorunu bulana kadar uzun ve zor hata ayıklayacaksınız.

Kendi kodunuzda, gerçekten bir tamsayı döngü değişkeni olurdu. iÇalışan numarayı değil , th düğmesini temsil eder .

Ve muhtemelen, ekstra netlik uğruna, yazmak olmaz i/2ama i*0.5yapar bunu bol bol neler olduğunu temizleyin.

var BUTTONS=11;
var STANDARD_LINE=3;

for(var i=0; i<BUTTONS; i++) {
    button.text = (i*0.5)+'';
    if (i==STANDARD_LINE) {
      button.color='red';
    }
}

Not: Yorumlarda belirtildiği gibi, JavaScript aslında tamsayılar için ayrı bir tür içermemektedir. Ancak 15 haneye kadar olan tamsayıların doğru / güvenli olduğu garanti edilir (bkz. Https://www.ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer ), bu nedenle bu gibi argümanlar için ("öyle tamsayılarla veya tamsayı olmayanlarla çalışmaya yatkın olan daha fazla kafa karıştırıcı / hata ") bu," ruh içinde "ayrı bir türe sahip olmak için uygun bir şekilde yakındır; Günlük kullanımda (döngüler, ekran koordinatları, dizi indeksleri vb.) NumberJavaScript olarak gösterilen tamsayı sayıları ile sürprizler olmaz .


BUTTONS ismini başka bir şeyle değiştirirdim - hepsinden sonra 11 düğme var ve 10 değil. Belki FIRST_BUTTON = 0, LAST_BUTTON = 10, STANDARD_LINE_BUTTON = 3. Bunun dışında, evet, böyle yapmalısınız.
gnasher729

Doğru, @EricDuminil ve bununla ilgili cevaba biraz ekledim. Teşekkür ederim!
AnoE

1

Önerilerinizden birinin iyi olduğunu sanmıyorum. Bunun yerine, maksimum değere ve boşluğa bağlı olarak düğme sayısı için bir değişken tanıtırdım. Daha sonra, düğme endeksleri üzerinde döngü oluşturmak için yeterince basittir.

function precisionRound(number, precision) {
  let factor = Math.pow(10, precision);
  return Math.round(number * factor) / factor;
}

var maxButtonValue = 5.0;
var buttonSpacing = 0.5;

let countEstimate = precisionRound(maxButtonValue / buttonSpacing, 5);
var buttonCount = Math.floor(countEstimate) + 1;

var highlightPosition = 3;
var highlightColor = 'red';

for (let i=0; i < buttonCount; i++) {
    let buttonValue = i / buttonSpacing;
    button.text = buttonValue.toString();
    if (i == highlightPosition) {
        button.color = highlightColor;
    }
}

Daha fazla kod olabilir, ancak daha okunaklı ve daha sağlamdır.


0

Döngü sayacını değer olarak kullanmak yerine, gösterdiğiniz değeri hesaplayarak her şeyi önleyebilirsiniz:

var MAX=5.0;
var DIFF=0.5
var STANDARD_LINE=1.5;

for(var i=0; (i*DIFF) < MAX ; i=i+1){
    var val = i * DIFF

    button.text=val+'';

    if(val==STANDARD_LINE){
      button.color='red';
    }
}

-1

Kayan nokta aritmetik yavaştır ve tamsayı aritmetik hızlıdır, bu yüzden kayan nokta kullandığım zaman, tam sayıların kullanılabileceği yerlerde gereksiz yere kullanmazdım. Kayan nokta sayılarını, sabitleri bile yaklaşık olarak küçük bir hatayla düşünmek faydalıdır. Yerel kayan nokta sayılarını, her sayıyı nokta yerine bir aralık olarak değerlendirdiğiniz artı / eksi kayan nokta nesnelerle değiştirmek, hata ayıklama sırasında çok kullanışlıdır. Bu şekilde, her aritmetik işlemden sonra artan artan yanlışlıkları ortaya çıkarırsınız. Bu yüzden "1.5", "1.45 ile 1.55 arasında bir sayı", "1.50", "1.495 ile 1.505 arasında" sayı olarak düşünülmelidir.


5
Tam sayılarla yüzdürme arasındaki performans farkı, küçük bir mikroişlemci için C kodu yazarken önemlidir, ancak modern x86 türevli CPU'lar o kadar hızlı kayan nokta yapar ki, herhangi bir ceza, dinamik bir dil kullanma yükü tarafından kolayca kapatılır. Özellikle, Javascript aslında her temsil etmiyor sayıyı gerektiğinde NaN yükünü kullanarak zaten kayan nokta olarak göstermiyor mu?
leftaroundabout

1
"Kayan nokta aritmetik yavaştır ve tamsayı aritmetik hızlıdır", ilerlemeye devam ederken müjde olarak saklamamanız gereken tarihsel bir gerçektir. @Leftaroundabout’un söylediğine eklemek için, cezanın neredeyse alakasız olacağı doğru değil, aynı zamanda , otomatikleştirilmiş derleyiciler ve çırpabilecek komut kümelerinin büyüsü sayesinde kayan nokta işlemlerini eşdeğer tamsayı işlemlerinden daha hızlı bulabilirsin. bir döngüde yüksek miktarda yüzer. Bu soru için önemli değil, ancak temel "tamsayı kayan noktadan daha hızlı" varsayımı bir süredir doğru değildi.
Jeroen Mostert

1
@JeroenMostert SSE / AVX, hem tam sayılar hem de değişkenler için vektörelleştirilmiş işlemlere sahiptir ve daha küçük tam sayılar kullanabilirsiniz (üstelik hiçbir bit boşa harcanmaz), bu nedenle , ilke olarak , çoğu zaman, en iyi duruma getirilmiş tamsayı kodundan daha fazla performans elde edebilirsiniz yüzen ile daha. Ancak yine, bu çoğu uygulama için geçerli değildir ve kesinlikle bu soru için değildir.
leftaroundabout

1
@leftaroundabout: Kesinlikle. Herhangi bir durumda hangisinin kesinlikle daha hızlı olduğu ile ilgili değil , sadece "FP'nin yavaş ve tamsayının hızlı olduğunu biliyorum, bu yüzden eğer mümkünse tamsayıları kullanacağım" demiştim. Yaptığınız şeyin optimizasyon gerektirip gerektirmediği sorusu.
Jeroen Mostert
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.