3 पॉइंट द्वारा GN⁺ 2023-09-28 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • 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 टिप्पणियां

 
GN⁺ 2023-09-28
Hacker News की रायें
  • यह दिलचस्प है कि hardcoded values और if स्टेटमेंट (या while) इस्तेमाल करने वाले सभी जवाब अधिकतम 5 comparisons करते हैं
    अगर units सिर्फ B, KiB, MiB, GiB, TiB, EiB तक हैं, तो अधिकतम 3 if statements से भी काम हो सकता है। GiB या उससे ऊपर है या नहीं, यह जांचने पर पता चल जाता है कि वह B/KiB/MiB नहीं है, इसलिए binary search जीतती है
    ZiB और YiB तक बढ़ाने पर भी अधिकतम 3 comparisons काफी हैं, जबकि hardcoding वाला तरीका अधिकतम 7 तक चला जाता है। अगर मैं खुद लिखता, तो log/pow/floating point का इस्तेमाल नहीं करता, क्योंकि गलती की संभावना बहुत ज्यादा है; if statements hardcode करता, लेकिन binary search के साथ

    • binary search वाला तरीका केवल 6 checks करने से धीमा भी हो सकता है। बाद वाले में संभव है कि सिर्फ 1 branch ली जाए, और branches बहुत धीमी होती हैं, इसलिए code को जितना हो सके straight-line flow में रखना बेहतर है
    • यह input distribution पर निर्भर करता है। अगर छोटे values बहुत आम हैं, तो linear search बेहतर हो सकती है
    • मुझे यह खराब engineering judgment लगता है। सरल solution को सहकर्मी आसानी से review कर सकता है, boundary conditions भी साफ दिखती हैं, और यह जांचना आसान होता है कि tests उन्हें cover करते हैं या नहीं
      ऐसे 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

  • समझ नहीं आ रहा। अगर 7 suffixes हैं, तो binary search से सही वाला चुन सकते हैं, और 3 comparisons काफी हैं। या फिर बस simple तरीके से करें तो भी 6 comparisons ही हैं
    log() दो बार, pow() एक बार और ceil() इस्तेमाल करना simple approach से बेहतर क्यों है, यह समझ नहीं आता। यहां बताया गया bug खुद इस बात का perfect example है कि जरूरत से ज्यादा clever बनने से क्या होता है

    • लगता है लेखक ने माना कि readability खराब है और फिर से loop वाले तरीके पर लौट गया: https://programming.guide/java/formatting-byte-size-to-human...
      फिर भी यह rounding bug को ध्यान में रखता है, इसलिए original post के पहले code example से थोड़ा बेहतर है
    • लेखक भी शुरुआत में कहता है कि यह सच में loop से बेहतर नहीं है
      और 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

    • 4-line solution को एक विशाल library से replace करने की culture ने ही left-pad पैदा किया था
  • यह मेरी पूरी तरह जिज्ञासा है, लेकिन क्या काफ़ी सारे developers StackOverflow के भरोसेमंद न होने वाले code को सीधे copy करके अपनी applications में paste कर देते हैं?
    यह अनुमान मशहूर है कि लोग StackOverflow से बस copy कर लेते हैं, लेकिन जब तक मैंने किसी को सच में ऐसा करते नहीं देखा, मुझे यह मज़ाक जैसा ही लगता था। मैं भी अनजान क्षेत्र में समस्या हल करते समय StackOverflow को starting point की तरह इस्तेमाल करता हूँ, लेकिन code को जस का तस कभी copy नहीं किया
    आम तौर पर snippet code ठीक वही नहीं करता जिसकी मुझे ज़रूरत होती है, इसलिए API देखनी पड़ती है और बताए गए approach के आधार पर अपना solution बनाना पड़ता है। ख़ासकर Python में StackOverflow ने कई बार उपयोगी niche API की दिशा बताई है

    • पहले मैंने एक ऐसे developer के साथ काम किया था जिसे answer देखते ही code में copy करने से कोई रोक नहीं सकता था। वह यह जाँचने के लिए question तक नहीं पढ़ता था कि समस्या उसकी जैसी है या नहीं, और answer भी नहीं पढ़ता था
      सचमुच 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 बहुत हैं
    • असल में ऐसा होता है, और जिस हिस्से को मैं अपने interest वाले program scope से बाहर मानता हूँ, उसमें यह और ज़्यादा बार होता है
      वैसे भी हम उन plumbing work वाले हिस्सों के लिए, जिनकी हमें ख़ास परवाह नहीं होती, अनजान लोगों का बनाया library code हमेशा इस्तेमाल करते हैं। अगर गहराई में जाकर समझना चाहें तो शायद खुद लिखेंगे, लेकिन अगर इस हिस्से को “बस चलने” देना है और project आगे बढ़ाना है, तो यह compiler error-driven development बन जाता है
    • लेखक ने जो कारण बताए हैं, उन्हीं की वजह से मैं लगभग कभी भी जस का तस copy/paste नहीं करता। इसके बजाय solution समझने की कोशिश करता हूँ, और ज़रूरत हो तो एक-एक line हाथ से लिखकर सही से समझता हूँ, फिर वहाँ से refactor करता हूँ
      variable names भी बदलता हूँ। foo, bar, baz इतने ज़्यादा होते हैं कि अक्सर इंसान के लिए पढ़ना मुश्किल हो जाता है। वही problem दोबारा मिले तो अंधाधुंध copy करने की तुलना में मुझे यह याद रखना भी आसान होता है कि मैंने क्या किया था
    • लोग सच में ऐसा करते हैं। StackOverflow से निकले गलत TLS code और configuration को बहुत बड़ी मात्रा में देखने के बाद, मुझे काफ़ी यकीन हो गया है कि ज़्यादातर systems certificates को ठीक से verify किए बिना ही चल रहे हैं
    • शायद आपको अभी तक Adderall खाए 23 साल के लोगों द्वारा बनाए गए codebase पर काम करने का सुख नहीं मिला है
  • अगर log 2 चाहिए, तो floating-point log क्यों इस्तेमाल किया जा रहा है, समझ नहीं आता
    अगर मैं कुछ miss नहीं कर रहा हूँ, तो नीचे वाला expression 2^63 bytes से छोटे positive values के लिए floor(log2(value)) बिल्कुल सही देता है और काफ़ी तेज़ है:
    Long.bitCount( (Long.highestOneBit(value) << 1) - 1) - 1

    • “सामान्य” units 10 की powers होते हैं, इसलिए यह तरीका सही नहीं है
  • snippet code देखते ही floating-point log operation और 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 करते थे

    • सोचता हूँ कि क्या दूसरों से ख़राब न दिखने की इंसानी प्रवृत्ति इस चीज़ को और बढ़ाती है। smart लोग भी bad ideas या जल्दबाज़ी वाले ideas को follow करके बेवकूफ़ी भरे नतीजों तक पहुँच सकते हैं
      public economic forecasts में भी कुछ ऐसा ही होता है। जो व्यक्ति अकेला गलत निकला जबकि बाकी सही थे, उसके साथ उन लोगों की तुलना में कहीं ज़्यादा कठोर व्यवहार होता है जो सबके साथ मिलकर गलत निकले
    • उस training का लक्ष्य क्या था?
  • ऐसे 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 करते हैं