- 2010 में लिखे गए Java
humanReadableByteCount उत्तर को 2018 के एक अध्ययन में सबसे ज़्यादा कॉपी किया गया Stack Overflow कोड स्निपेट पाया गया, लेकिन यह byte size format की boundary values पर गलत नतीजे देता था
- यह कोड
kB, MB, GB जैसे prefixes के 1000 या 1024 की powers होने का इस्तेमाल करके, loop के बजाय log calculation से unit चुनता था
- मुख्य bug rounding boundary value की समस्या थी, जहाँ SI mode में
999,999 bytes का output "1000.0 kB" आता था; specification के हिसाब से अगर numeric range 1 से 999.9 तक होनी चाहिए, तो "1.0 MB" सही है
- बड़े values पर
double की floating-point precision limit भी जुड़ गई, जिससे 999,949,999,999,999,999 input पर 1000.0 PB आया; correction के लिए threshold calculation और scale reduction, bit pattern correction, strictfp की जरूरत पड़ी
- Final code negative values और
Long.MIN_VALUE तक handle करता है, लेकिन original simplicity खो बैठा; Stack Overflow code copy करते समय edge-case tests और attribution भी साथ में जरूरी हैं
2010 के उत्तर ने जिस simplification को निशाना बनाया
- समस्या byte count को इंसानों के पढ़ने लायक string में format करने की थी
- उदाहरण:
123,456,789 bytes को "123.5 MB" जैसा output करना
- implicit specification यह था कि result string का numeric हिस्सा 1 से 999.9 के बीच हो और उसके साथ appropriate size suffix लगे
- मौजूदा उत्तर
EB, PB, TB, GB, MB, kB, B को बड़े units से iterate करते हुए byte count से छोटा पहला unit चुनने वाला loop-based approach था
- नए उत्तर ने loops और branches कम करने के लिए
Math.log और Math.pow का इस्तेमाल किया
- SI mode में unit
1000 होता है
- binary notation में unit
1024 होता है
exp = log(bytes) / log(unit) value को integer में बदलकर prefix index के रूप में इस्तेमाल किया
- Prefixes SI में
"kMGTPE", binary में "KMGTPE" इस्तेमाल होते हैं और binary में "i" जोड़ा जाता है
Copying की स्थिति और OpenJDK episode
- Sebastian Baltes का paper Usage and Attribution of Stack Overflow Code Snippets in GitHub Projects यह analyze करता है कि Stack Overflow code snippets GitHub projects में कैसे इस्तेमाल होते हैं और attribution कैसे दिया जाता है
- Analysis method यह था कि Stack Overflow data dump से code snippets extract किए गए और उन्हें public GitHub repositories के code से compare किया गया
- मुख्य सवाल यह था कि Stack Overflow के CC BY-SA 3.0 license के अनुरूप attribution दिया जा रहा है या नहीं
- नतीजतन, अधिकतर users ने proper attribution शामिल नहीं किया था
- Answer ID 3758880 paper की table में सबसे ऊपर था, और उस समय उसके hundreds of thousands views और 1,000 से ज़्यादा upvotes थे
- GitHub पर
humanReadableByteCount search करने पर हजारों usage cases मिलते थे, और local repository में इसे नीचे दिए command से check किया जा सकता है
git grep humanReadableByteCount
- OpenJDK repository में भी matching case मिला
- उस code में attribution नहीं था, और OpenJDK license CC BY-SA 3.0 के साथ compatible नहीं था
- Sebastian Baltes ने OpenJDK development mailing list पर पूछा कि code Stack Overflow से OpenJDK में copy किया गया था या उल्टा
- Answer author उस commit के merge होने से पहले Oracle में शामिल नहीं हुए थे, और उन्होंने उस patch में भी contribute नहीं किया था
- बाद में issue register हुआ और code remove कर दिया गया
पहला bug: 999 से भरी boundary value
- ऊपर से संदेहास्पद लगने वाली समस्याएँ असली कारण नहीं थीं
long की maximum value 2^63 - 1, लगभग 9.2 × 10^18 है, इसलिए यह EB के बाद के units तक नहीं जाती
bytes < unit वाले case को पहला if handle कर देता है, इसलिए exp 0 होकर charAt(exp - 1) fail नहीं होता
- असली समस्या rounding boundary value थी
- Input
999,999 bytes SI mode में "1000.0 kB" बन जाता है
- Specification के अनुसार numeric हिस्सा 1 से 999.9 के बीच होना चाहिए, इसलिए सही result
"1.0 MB" है
- लिखे जाने के समय तक posted 22 answers सभी में, Apache Commons और Android libraries इस्तेमाल करने वाले answers तक में, यह bug या इसका कोई variant था
- Solution का core यह तय करने वाला threshold है कि exponent
exp को कब अगले unit पर बढ़ाया जाए
k से M में बदलने का point 999,950 है, जहाँ value 999.9 k की तुलना में 1 MB के ज़्यादा करीब हो जाती है
M से G में बदलने का point 999,950,000 है
- Binary mode में threshold integer नहीं होता, इसलिए
ceil की जरूरत होती है
if (bytes >= Math.ceil(Math.pow(unit, exp) * (unit - 0.05)))
exp++;
दूसरा bug: double precision limit
- ऊपर वाला correction apply करने के बाद भी
999,949,999,999,999,999 input का output 1000.0 PB आया, जबकि सही result 999.9 PB था
- वजह mathematical expression खुद नहीं, बल्कि double precision limit थी
- IEEE 754 representation में 0 के पास floating-point values घनी होती हैं, लेकिन बड़े values बहुत sparse होते हैं
- बहुत बड़े
double में Long.MAX_VALUE subtract करने पर भी value बदल नहीं सकती
double a = Double.MAX_VALUE;
double b = a - Long.MAX_VALUE;
System.err.println(a == b); // prints true
- Problematic calculation दो जगह होती है
String.format argument में की जाने वाली division
exp बढ़ाना है या नहीं, यह तय करने वाली threshold calculation
- पहली समस्या को intermediate
bytes value को बेहतर precision वाली range में घटाकर और exp adjust करके handle किया गया
- Assumption यह है कि final result वैसे भी rounded होगा, इसलिए lower digits को discard किया जा सकता है
if (exp > 4) {
bytes /= unit;
exp--;
}
- दूसरी समस्या में lower bits important थे
999,949,99…9 और 999,950,00…0 को अलग-अलग exponents में classify होना चाहिए
- Possible thresholds SI और binary मिलाकर 12 हैं, और उनमें से सिर्फ एक गलत result दे रहा था
- गलत result को
D00 पर खत्म होने वाले bit pattern से identify करके correct किया गया
- क्योंकि यह specific floating-point result के bit pattern पर depend करता है, इसलिए
strictfp लगाया गया
Negative input और final code
- Java में unsigned
long नहीं है, इसलिए negative byte counts handle करना भी जोड़ा गया
- पहले
-10,000 input का output -10000 B आता था
absBytes introduce करके exp related calculations absolute value के आधार पर की गईं
Long.MIN_VALUE को special handling चाहिए थी
- क्योंकि
-Long.MIN_VALUE == Long.MIN_VALUE होता है
- इसलिए अगर
bytes == Long.MIN_VALUE हो, तो Long.MAX_VALUE इस्तेमाल किया गया, वरना Math.abs(bytes) इस्तेमाल हुआ
- Final version में
strictfp, threshold correction, Long.MIN_VALUE handling, और बड़े exponent पर scale reduction शामिल हैं
- Loops और excessive branches से बचने की कोशिश करने वाला code सभी corner cases polish करने के बाद original version से ज़्यादा पढ़ने में मुश्किल code बन गया
- Production-quality modern code के लिए अलग लेख Formatting byte size to human readable format देख सकते हैं
Practical lessons
- Stack Overflow code snippet में हजारों upvotes हों, फिर भी उसमें bug हो सकता है
- Copied code के लिए खास तौर पर edge-case tests जरूरी हैं
- Floating-point arithmetic boundary values और बड़े numbers पर handle करना मुश्किल होता है
- Code copy करते समय proper attribution जरूरी है; वरना यह वास्तविक problem बन सकता है
1 टिप्पणियां
Hacker News की रायें
यह दिलचस्प है कि hardcoded values और
ifस्टेटमेंट (याwhile) इस्तेमाल करने वाले सभी जवाब अधिकतम 5 comparisons करते हैंअगर units सिर्फ B, KiB, MiB, GiB, TiB, EiB तक हैं, तो अधिकतम 3
ifstatements से भी काम हो सकता है। GiB या उससे ऊपर है या नहीं, यह जांचने पर पता चल जाता है कि वह B/KiB/MiB नहीं है, इसलिए binary search जीतती हैZiB और YiB तक बढ़ाने पर भी अधिकतम 3 comparisons काफी हैं, जबकि hardcoding वाला तरीका अधिकतम 7 तक चला जाता है। अगर मैं खुद लिखता, तो
log/pow/floating point का इस्तेमाल नहीं करता, क्योंकि गलती की संभावना बहुत ज्यादा है;ifstatements hardcode करता, लेकिन binary search के साथऐसे code में आप ज्यादा काम करके ऐसा code लिख रहे होते हैं जो धीमा, ज्यादा complex, और test/review करने में भी ज्यादा मुश्किल है
(2019) पिछली चर्चाएं:
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 - जून 2021, 334 comments
The most copied StackOverflow snippet of all time is flawed - https://news.ycombinator.com/item?id=21698619 - दिसंबर 2019, 88 comments
The most copied StackOverflow snippet of all time is flawed - https://news.ycombinator.com/item?id=21693431 - दिसंबर 2019, 3 comments
समझ नहीं आ रहा। अगर 7 suffixes हैं, तो binary search से सही वाला चुन सकते हैं, और 3 comparisons काफी हैं। या फिर बस simple तरीके से करें तो भी 6 comparisons ही हैं
log()दो बार,pow()एक बार औरceil()इस्तेमाल करना simple approach से बेहतर क्यों है, यह समझ नहीं आता। यहां बताया गया bug खुद इस बात का perfect example है कि जरूरत से ज्यादा clever बनने से क्या होता हैफिर भी यह rounding bug को ध्यान में रखता है, इसलिए original post के पहले code example से थोड़ा बेहतर है
और 6 comparisons सिर्फ maximum value के case में हैं; real usage में ऐसा होने की संभावना कम लगती है। अगर ज्यादातर values B या KB range में हैं, तो linear तरीका बेहतर हो सकता है
बेशर्म promotion है, लेकिन S/O से copy करने के बजाय अगर human-readable format में size को fast और accurate तरीके से format करना हो, तो हमारी open source PrettySize library भी इस्तेमाल कर सकते हैं। Rust के लिए [0] और .NET के लिए [1] है, और file sizes पर type-safe logical operations को भी safe और आसान बनाती है
S/O snippet 4 lines का है, लेकिन ये libraries कहीं ज्यादा comprehensive हैं और tests, output format options, size conversion आदि शामिल करती हैं
[0]: https://github.com/neosmart/prettysize-rs
[1]: https://github.com/neosmart/PrettySize.net
यह मेरी पूरी तरह जिज्ञासा है, लेकिन क्या काफ़ी सारे developers StackOverflow के भरोसेमंद न होने वाले code को सीधे copy करके अपनी applications में paste कर देते हैं?
यह अनुमान मशहूर है कि लोग StackOverflow से बस copy कर लेते हैं, लेकिन जब तक मैंने किसी को सच में ऐसा करते नहीं देखा, मुझे यह मज़ाक जैसा ही लगता था। मैं भी अनजान क्षेत्र में समस्या हल करते समय StackOverflow को starting point की तरह इस्तेमाल करता हूँ, लेकिन code को जस का तस कभी copy नहीं किया
आम तौर पर snippet code ठीक वही नहीं करता जिसकी मुझे ज़रूरत होती है, इसलिए API देखनी पड़ती है और बताए गए approach के आधार पर अपना solution बनाना पड़ता है। ख़ासकर Python में StackOverflow ने कई बार उपयोगी niche API की दिशा बताई है
सचमुच Google → पहला दिखने वाला Stack Overflow link click → पहला दिखने वाला code block copy/paste था, और कभी-कभी तो language भी अलग होती थी। pair programming के दौरान input device को physically छीनना पड़ता था। अगर कहो कि यह गलत है, तो मेरी बात पूरी होने से पहले ही वह page का दूसरा code snippet paste कर रहा होता था, और अजीब तरह से बहुत तेज़ था
यह extreme case है, लेकिन “code चाहिए; Stack Overflow पर code है; problem solved!” वाली सोच के साथ यह सोचे बिना कि solution सही है या नहीं, काम करने वाले developers बहुत हैं
वैसे भी हम उन plumbing work वाले हिस्सों के लिए, जिनकी हमें ख़ास परवाह नहीं होती, अनजान लोगों का बनाया library code हमेशा इस्तेमाल करते हैं। अगर गहराई में जाकर समझना चाहें तो शायद खुद लिखेंगे, लेकिन अगर इस हिस्से को “बस चलने” देना है और project आगे बढ़ाना है, तो यह compiler error-driven development बन जाता है
variable names भी बदलता हूँ।
foo,bar,bazइतने ज़्यादा होते हैं कि अक्सर इंसान के लिए पढ़ना मुश्किल हो जाता है। वही problem दोबारा मिले तो अंधाधुंध copy करने की तुलना में मुझे यह याद रखना भी आसान होता है कि मैंने क्या किया थाअगर
log 2चाहिए, तो floating-point log क्यों इस्तेमाल किया जा रहा है, समझ नहीं आताअगर मैं कुछ miss नहीं कर रहा हूँ, तो नीचे वाला expression 2^63 bytes से छोटे positive values के लिए
floor(log2(value))बिल्कुल सही देता है और काफ़ी तेज़ है:Long.bitCount( (Long.highestOneBit(value) << 1) - 1) - 1snippet code देखते ही floating-point
logoperation और integer पर division दिखे, तो मुझे लगा कि यह बहुत clever तरीके से लिखा गया है और इसी वजह से मूल रूप से bug-prone code है; मैंने दिमाग़ में तुरंत इसे discard कर दियाज्ञान की chain बिल्कुल नीचे तक जाती है। यह दिखाता है कि बहुत छोटा-सा ज्ञान भी एक बार बाहर निकालने के बाद वापस रखना कितना मुश्किल है
Stack Exchange जिस तरह active contributors को तेज़ी से खो रहा है, उसमें मैं सोचता हूँ कि बाद में गलत साबित हुए fastest-gun answers को सुधारने के लिए क्या चाहिए होगा। और जब ऐसे “थोड़े गलत” answers search history और धीरे-धीरे LLM history में पक्के हो जाएँगे, तो हमारे collective knowledge के लिए इसका क्या मतलब होगा
मुझे basic military training का समय याद आता है। instructors जानबूझकर recruits को ऐसा task बिना instructions के दे कर चले जाते थे, जिसे कोई करना नहीं जानता था
फिर कोई न कोई हमेशा गलत तरीके से शुरू करता था, और बाकी सब उसी को follow करते थे
public economic forecasts में भी कुछ ऐसा ही होता है। जो व्यक्ति अकेला गलत निकला जबकि बाकी सही थे, उसके साथ उन लोगों की तुलना में कहीं ज़्यादा कठोर व्यवहार होता है जो सबके साथ मिलकर गलत निकले
ऐसे algorithms में floating-point error को मैं ज़रूरी नहीं कि “defect” मानूँ। अगर code logical और mathematical रूप से सही solution define करता है, तो अपने-आप में वह “सही” है
floating-point error को handle करना उससे एक स्तर ऊपर की चीज़ है, और असल में तभी किया जाने वाला काम है जब वह महत्वपूर्ण हो। मैं एक perfect भविष्य की programming language की कल्पना कर सकता हूँ जहाँ floating-point errors मौजूद ही न हों और उन्हें consider करने की ज़रूरत न पड़े; मेरे 99% algorithms मानो ऐसी ही language को target करते हैं