- 2010’da yazılan Java
humanReadableByteCount yanıtı, 2018 tarihli bir araştırmada en çok kopyalanan Stack Overflow kod parçası olarak belirlendi; ancak bayt boyutu biçimlendirmesinde sınır değerlerde hatalı sonuçlar üretiyordu
- Bu kod,
kB, MB, GB gibi öneklerin 1000’in ya da 1024’ün kuvvetleri olmasından yararlanarak bir döngü yerine logaritma hesabıyla birim seçiyordu
- Temel hata, SI modunda
999,999 bytes değerinin "1000.0 kB" olarak çıktığı yuvarlama sınır değeri sorunuydu; belirtime göre sayı aralığı 1 ile 999.9 arasındaysa doğru sonuç "1.0 MB" olmalıydı
- Daha büyük değerlerde buna
double türünün kayan nokta hassasiyeti sınırı da eklendi; 999,949,999,999,999,999 girdisi 1000.0 PB olarak çıktı ve düzeltme için eşik değeri hesaplama, ölçek küçültme, bit deseni düzeltmesi ve strictfp gerekti
- Son kod negatif sayıları ve
Long.MIN_VALUE değerini bile işliyor, ancak ilk halindeki sadeliği kaybediyor; Stack Overflow’dan kod kopyalarken uç durum testleri ve kaynak gösterimi de gerekli
2010’daki yanıtın hedeflediği sadeleştirme
- Sorun, bayt sayısını insanların okuyabileceği bir dizeye biçimlendirmekti
- Örnek:
123,456,789 bytes değerini "123.5 MB" gibi yazdırmak
- Örtük belirtim, sonuç dizesindeki sayısal kısmın 1 ile 999.9 arasında olması ve uygun bir boyut soneki taşımasıydı
- Mevcut yanıt,
EB, PB, TB, GB, MB, kB, B değerlerini büyük birimden başlayarak dolaşıp bayt sayısından küçük olan ilk birimi seçen döngü tabanlı bir yaklaşımdı
- Yeni yanıt, döngüleri ve dallanmaları azaltmak için
Math.log ve Math.pow kullanıyordu
- SI modunda birim
1000
- İkili gösterimde birim
1024
exp = log(bytes) / log(unit) değerini tam sayıya çevirip önek indeksi olarak kullanıyordu
- Önekler SI için
"kMGTPE", ikili için "KMGTPE" idi; ikili gösterimde ayrıca "i" ekleniyordu
Kopyalanma durumu ve OpenJDK bölümü
- Sebastian Baltes’in Usage and Attribution of Stack Overflow Code Snippets in GitHub Projects makalesi, Stack Overflow kod parçalarının GitHub projelerinde nasıl kullanıldığını ve kaynak gösteriminin nasıl yapıldığını analiz eder
- Analiz yöntemi, Stack Overflow veri dökümünden kod parçalarını çıkarıp bunları herkese açık GitHub depolarındaki kodlarla karşılaştırmaktı
- Temel soru, Stack Overflow’un CC BY-SA 3.0 lisansına uygun kaynak gösteriminin yapılıp yapılmadığıydı
- Sonuçta kullanıcıların çoğu uygun kaynak gösterimi eklememişti
- Yanıt ID’si 3758880 makaledeki tabloda en üstteydi ve o dönemde yüz binlerce görüntülenmeye ve 1.000’den fazla upvote’a sahipti
- GitHub’da
humanReadableByteCount arandığında binlerce kullanım örneği çıkıyordu; yerel bir depoda şu komutla kontrol edilebilir
git grep humanReadableByteCount
- OpenJDK deposunda da eşleşen bir örnek bulundu
- İlgili kodda kaynak gösterimi yoktu ve OpenJDK lisansı CC BY-SA 3.0 ile uyumlu değildi
- Sebastian Baltes, OpenJDK geliştirme e-posta listesinde kodun Stack Overflow’dan OpenJDK’ya mı kopyalandığını, yoksa tersinin mi olduğunu sordu
- Yanıtı yazan kişi, ilgili commit birleştirilmeden önce Oracle’da çalışmaya başlamamıştı ve o yamaya da katkıda bulunmamıştı
- Daha sonra bir issue açıldı ve kod kaldırıldı
İlk hata: 999’larla devam eden sınır değer
- Dışarıdan şüpheli görünen sorunlar asıl neden değildi
long türünün en büyük değeri 2^63 - 1, yani yaklaşık 9.2 × 10^18 olduğundan EB sonrasındaki birimlere taşmaz
bytes < unit durumunu ilk if işlediği için exp 0 olup charAt(exp - 1) de hata vermez
- Gerçek sorun yuvarlama sınır değeriydi
999,999 bytes girdisi SI modunda "1000.0 kB" olur
- Sayısal kısmın 1 ile 999.9 arasında olması gerektiğini söyleyen belirtime göre doğru sonuç
"1.0 MB"dir
- Yazıldığı dönem itibarıyla yayımlanmış 22 yanıtın tamamında, Apache Commons ve Android kitaplıklarını kullanan yanıtlar dahil, bu hata ya da bir türevi vardı
- Çözümün özü,
exp üssünün ne zaman bir sonraki birime yükseltileceğini belirleyen eşik değeridir
kden Mye geçiş noktası, değerin 999.9 kden çok 1 MBye daha yakın olduğu 999,950dir
Mden Gye geçiş noktası 999,950,000dir
- İkili modda eşik değer tam sayı olmadığından
ceil gerekir
if (bytes >= Math.ceil(Math.pow(unit, exp) * (unit - 0.05)))
exp++;
İkinci hata: double hassasiyeti sınırı
- Yukarıdaki düzeltme uygulansa bile
999,949,999,999,999,999 girdisi 1000.0 PB olarak yazdırılıyordu; doğru sonuç 999.9 PB idi
- Neden matematiksel formülün kendisi değil, double hassasiyeti sınırıydı
- IEEE 754 gösteriminde 0’a yakın kayan nokta değerleri sık aralıklıdır, ancak büyük değerler çok seyrektir
- Çok büyük bir
double değerden Long.MAX_VALUE çıkarılsa bile değer değişmeyebilir
double a = Double.MAX_VALUE;
double b = a - Long.MAX_VALUE;
System.err.println(a == b); // prints true
- Sorunlu hesaplama iki yerde gerçekleşir
String.format argümanında yapılan bölme işlemi
exp değerinin artırılıp artırılmayacağını belirleyen eşik değeri hesaplaması
- İlk sorun, ara
bytes değerini hassasiyetin daha iyi olduğu bir aralığa küçültüp exp değerini ayarlayarak ele alınır
- Nihai sonuç zaten yuvarlanacağı için düşük basamakların atılabileceği varsayılır
if (exp > 4) {
bytes /= unit;
exp--;
}
- İkinci sorunda düşük bitler önemliydi
999,949,99…9 ile 999,950,00…0 farklı üslere sınıflandırılmalıydı
- Olası eşik değerleri SI ve ikili birlikte 12 taneydi; bunlardan yalnızca biri hatalı sonuç veriyordu
- Hatalı sonuç,
D00 ile biten bit deseniyle tanımlanıp düzeltildi
- Belirli bir kayan nokta sonucunun bit desenine bağlı olduğundan
strictfp eklendi
Negatif girdi ve son kod
- Java’da unsigned
long olmadığı için negatif bayt sayısı işleme de eklendi
- Önceden
-10,000 girdisi -10000 B olarak yazdırılıyordu
absBytes eklenerek exp ile ilgili hesaplamalar mutlak değer üzerinden yapıldı
Long.MIN_VALUE için özel işlem gerekiyordu
- Çünkü
-Long.MIN_VALUE == Long.MIN_VALUE
- Bu yüzden
bytes == Long.MIN_VALUE ise Long.MAX_VALUE, diğer durumlarda Math.abs(bytes) kullanılır
- Son sürüm
strictfp, eşik değeri düzeltmesi, Long.MIN_VALUE işleme ve büyük üslerde ölçek küçültmeyi içerir
- Döngülerden ve aşırı dallanmadan kaçınmaya çalışan kod, tüm köşe durumları düzeltildikten sonra özgün sürümden daha zor okunur bir koda dönüştü
- Üretim kalitesinde güncel kod için ayrı yazı olan Formatting byte size to human readable format incelenebilir
Pratikte kalan dersler
- Stack Overflow kod parçaları binlerce upvote alsa bile hata içerebilir
- Kopyalanan kod için özellikle uç durum testleri gerekir
- Kayan nokta aritmetiği sınır değerlerde ve büyük sayılarda yönetmesi zordur
- Kod kopyalarken uygun kaynak gösterimi gerekir; aksi halde bu gerçek bir soruna dönüşebilir
1 yorum
Hacker News yorumları
Sabit kodlanmış değerler ve
if(veyawhile) kullanan yanıtların hepsinin en fazla 5 karşılaştırma yapması ilginçBirimler yalnızca B, KiB, MiB, GiB, TiB, EiB ise en fazla 3
ififadesiyle de çözülebilir. GiB veya üstü olup olmadığını kontrol edince B/KiB/MiB olmadığını anlarsınız; bu yüzden ikili arama kazanırZiB ve YiB’ye kadar genişletseniz bile en fazla 3 karşılaştırma yeterli olur; sabit kodlama yaklaşımı ise en fazla 7’ye çıkar. Kendim yazsaydım
log/pow/kayan nokta kullanmazdım, hata yapma olasılığı çok yüksek;ififadelerini sabit kodlar ama ikili aramayla yapardımBu tür kod, daha yavaş, daha karmaşık, test etmesi ve gözden geçirmesi daha zor kod yazmak için çok uğraşmak anlamına gelir
(2019) Önceki tartışmalar:
https://news.ycombinator.com/item?id=21693431
https://news.ycombinator.com/item?id=21698619
https://news.ycombinator.com/item?id=27533684
The most copied StackOverflow snippet of all time is flawed (2019) - https://news.ycombinator.com/item?id=27533684 - Haziran 2021, 334 yorum
The most copied StackOverflow snippet of all time is flawed - https://news.ycombinator.com/item?id=21698619 - Aralık 2019, 88 yorum
The most copied StackOverflow snippet of all time is flawed - https://news.ycombinator.com/item?id=21693431 - Aralık 2019, 3 yorum
Anlamıyorum. 7 sonek varsa ikili arama ile doğru olanı seçersiniz, 3 karşılaştırma yeter. Ya da dümdüz basit yapsanız bile 6 karşılaştırma
İki kez
log(), bir kezpow(),ceil()kullanmanın basit yaklaşımdan neden daha iyi olduğunu anlamıyorum. Burada anlatılan hatanın kendisi, fazla akıllı davranmaya çalışınca ne olduğuna dair mükemmel bir örnekYine de yuvarlama hatasını hesaba kattığı için özgün metindeki ilk kod örneğinden biraz daha iyi
Ayrıca 6 karşılaştırma yalnızca en büyük değer için geçerli; gerçek kullanımda bunun pek olası görünmüyor. Değerlerin çoğu B veya KB aralığındaysa doğrusal yaklaşım daha iyi olabilir
Yüzsüz bir reklam olacak ama S/O’dan kopyalamak yerine boyutları insan tarafından okunabilir biçimde hızlı ve doğru formatlamak için bizim açık kaynak PrettySize kütüphanemizi de kullanabilirsiniz. Rust için [0] ve .NET için [1] sürümleri var; dosya boyutları üzerinde tip güvenli mantıksal işlemleri de güvenli ve kolay hale getiriyor
S/O parçacığı 4 satır, ama bu kütüphaneler çok daha kapsamlı ve testler, çıktı formatı seçenekleri, boyut dönüşümleri vb. içeriyor
[0]: https://github.com/neosmart/prettysize-rs
[1]: https://github.com/neosmart/PrettySize.net
Tamamen meraktan soruyorum: StackOverflow’daki güvenilir olmayan kodu öylece kopyalayıp uygulamalarına yapıştıran epey geliştirici var mı?
İnsanların StackOverflow’dan doğrudan kopyaladığı varsayımı meşhur, ama birinin gerçekten yaptığını görene kadar bunu daha çok şaka gibi düşünüyordum. Ben de pek aşina olmadığım bir alanda sorun çözerken StackOverflow’u başlangıç noktası olarak kullanıyorum, ama kodu olduğu gibi kopyaladığım olmadı.
Genelde kod parçacığı tam olarak ihtiyacım olan şeyi yapmadığı için API’ye bakmam ve anlatılan yaklaşımı temel alarak kendi çözümümü oluşturmam gerekiyor. Özellikle Python’da StackOverflow’un işe yarar niş API yönünü gösterdiği çok oldu.
Kelimenin tam anlamıyla Google → ilk görünen Stack Overflow bağlantısına tıklama → ilk görünen kod bloğunu kopyala/yapıştır şeklindeydi; bazen dil bile farklı olurdu. Pair programming sırasında giriş cihazını fiziksel olarak elinden almamız gerekirdi. Yanlış olduğunu söylediğinizde, daha sözünüz bitmeden sayfadaki ikinci kod parçacığını yapıştırıyor olurdu; tuhaf derecede hızlıydı.
Uç bir örnek ama “Koda ihtiyacım var; Stack Overflow’da kod var; çözüldü!” zihniyetiyle bunun uygun bir çözüm olup olmadığını hiç düşünmeyen çok geliştirici var.
Zaten pek umursamadığımız tesisat işleri kısmı için yabancıların yazdığı kütüphane kodunu sürekli alıp kullanıyoruz. Derinine inip anlamak istiyorsanız muhtemelen kendiniz yazarsınız; ama bu kısmın “sadece çalışmasını” sağlayıp projeye devam etmek istiyorsanız iş derleyici hatası güdümlü geliştirmeye dönüşüyor.
Değişken adlarını da değiştiririm. Çünkü çoğu zaman
foo,bar,bazo kadar çoktur ki insanın okuması zorlaşır. Aynı sorunla tekrar karşılaşırsam, körlemesine kopyalamış olmama kıyasla ne yaptığımı hatırlamam da daha kolay olur.log 2gerekiyorken neden kayan noktalı logaritma kullanıldığını anlamıyorum.Bir şeyi kaçırmıyorsam, aşağıdaki ifade 2^63 bayttan küçük pozitif değerler için
floor(log2(value))sonucunu doğru verir ve çok daha hızlıdır:Long.bitCount( (Long.highestOneBit(value) << 1) - 1) - 1Kod parçacığını görür görmez kayan noktalı
logişlemini ve tam sayılar üzerinde bölmeyi fark ettim; aşırı kurnazca yazıldığı için doğası gereği hataya açık bir kod olduğunu düşünüp kafamda hemen eledim.Bilgi zinciri sonuna kadar iner. Çok küçük bir bilgiyi bile bir kez ortaya çıkardıktan sonra geri koymanın ne kadar zor olduğunu gösteriyor.
Stack Exchange’in aktif katkı verenleri hızla kaybettiği bir ortamda, sonradan yanlış yöne saptığı anlaşılan hızlı silahşör cevaplarını düzeltmek için ne gerekeceğini merak ediyorum. Ayrıca bu tür “azıcık yanlış” cevaplar arama geçmişine ve giderek LLM tarihine yerleştikçe kolektif bilgimiz açısından bunun ne anlama geleceğini de merak ediyorum.
Temel askerî eğitim zamanımı hatırlattı. Eğitmenler, acemilere kimsenin nasıl yapılacağını bilmediği bir görevi özellikle talimatsız verir ve giderlerdi.
Sonra biri her zaman yanlış şekilde başlar, geri kalan herkes de onu takip ederdi.
Açık ekonomik tahminlerde de benzer bir şey yaşanıyor. Başkaları doğru tahmin etmişken tek başına yanılan kişi, herkesle birlikte yanılan kişiden çok daha sert muamele görüyor.
Bu tür algoritmalarda kayan nokta hatasını mutlaka “kusur” olarak görmem. Kod mantıksal ve matematiksel olarak doğru bir çözüm tanımlıyorsa, kendi başına “doğru” kabul ederim.
Kayan nokta hatalarını çözmek bunun bir üst aşaması ve yalnızca gerçekten önemli olduğunda yapılan bir iştir. Kayan nokta hatalarının var olmadığı, bu yüzden onları dikkate almanın bile gerekmediği mükemmel bir geleceğin programlama dilini hayal edebiliyorum; algoritmalarımın %99’u da sanki böyle bir dili hedefliyor.