اشتباه در ساخت sequence در تابع range

clojure

#1

باز هم من و یه تابع که نمیتونم رفتارشو توجیه کنم :neutral_face:
این قسمت، تابع range.

میدونیم که range یه lazy sequence میسازه. مثلا این:

user=> (range 5)
(0 1 2 3 4)

و اگه بخوایم هر قدم به اندازه‌ی 0.5 باشه میشه این:

user=> (range 0 5 0.5)
(0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5)

تا اینجا همه چیز تحت کنترله. ولی چرا وقتی میخوایم به میزان 0.1 قدم پیش بریم، اینطوری میشه؟

user=> (take 10 (range 0 5 0.1))
(0 0.1 0.2 0.30000000000000004 0.4 0.5 0.6 0.7 0.7999999999999999 0.8999999999999999)

کسی میدونه چرا Rich Hickey با من از این شوخیا میکنه؟ :joy:
ببینید منطقی نیست دیگه. اینو به خودش نشون بدیم قبول نمیکنه.

(= 0.30000000000000004 0.3)

سورس کدی که توی داکیومنت لینک داده بود برای range اینه که چون طولانیه، یه تیکه‌ی مهمشو اینجا مینویسم:

(clojure.lang.LongRange/create start end step)

که واضحه داره یه کد جاوا رو اجرا میکنه. این کد:

return new LongRange(start, end, step, positiveStep(end));

ولی مغزم خسته‌تر از چیزیه که ادامه‌ی داستانو رمزگشایی کنم.


#2

این رفتار اصلا اشتباه نیست - خطا هست اما نه خطای کلوژر - و در همه زبان های دیگه هم وجود داره، برای این نوع کالکشن باید از BigDecimal استفاده کنی. این شوخی رو بیشتر JVM و پردازنده با شما میکنند تا Rich


#3

توی کتابای ریاضی که برای دانشجوهای انفورماتیک مینویسن، معمولاً این موضوع رو توضیح دادن. مشکل به نمایش عدد 0.1 تو دستگاه اعداد در مبنای 2 ربط داره. توی Python هم اگه 0.1 و 0.2 رو جمع کنی 0.30000000000000004 به دست میاری.
ولی توی Common Lisp تست کردم، جواب 0.3 بود. تقسیم ها رو هم موقع نمایش به صورت کسر گویا نشون میده، ولی وقتی بخوای محاسبه انجام بدی، مقدار دقیقش رو استفاده میکنه. از این بابت جالبه.


#4

حدث میزدم. چون کد خاصی تو کلوژر نوشته نشده و تهش داره میرسه به کدهای جاوا.

جالب بود. نمیدونستم.

توی کلوژر هم به صورت کسری (ratio) نشون میده (احتمالا تنبل‌بازیهای laziness هست) ولی اگه یکی از عناصر تقسیم عدد اعشاری باشه اونوقت جواب رو درست نشون میده.

user=> (/ 1 10)
1/10
user=> (/ 1 10.0)
0.1

مثل اینکه همه جا همینطوریه:

user=> (+ 0.2 0.1)
0.30000000000000004
user=> (map #(reduce + (repeat % 0.1)) (range 10))
(0 0.1 0.2 0.30000000000000004 0.4 0.5 0.6 0.7 0.7999999999999999 0.8999999999999999)

خدا به خیر بگذرونه. شوخی خطرناکیه.


پ.ن:
میشه درستش کرد ولی…

user=> (format "%.2f" (+ 0.2 0.1))
"0.30"

#5

Lazy رو با این مورد قاطی نکنیم بهتره، خیلی ربطی نداره.
منظور من از jvm کدهای جاوا نبود، خود jvm بود :slight_smile:


#6

میدونم. منم منظورم دقیقا lazy sequence نبود.
ولی به نظرم به همون دلیل «فشار الکی به سخت افزار نیاد» نگه میداره هروقت لازم بود عملیات رو انجام میده.


#7

منظور شما batch processing هست؟ یا مشکل BigDecimal؟ احتمالا من اشتباه متوجه شدم


#8

نمیدونم دقیقا بهش چی میگن.
هنوز خیلی چیزا هست که باید یاد بگیرم. برنامه مینویسم ولی سواد کامپیوتریم ناقصه. همین چندوقت پیش (وقتی با کلوژر آشنا شدم) فهمیدم immutable یعنی چی و تا یه مدت نمیفهمیدم چطور میشه تو این وضعیت یه نرم‌افزار ساخت که واقعا یه کاری انجام بده!


#9

فقط یک نکته وجود داره و اونم اینه که در بیشتر موارد -در بیشتر موراد نه همه موارد- اشتباه از ما هست نه کمپایلر یا مفسر، پس فکر کردن مثل تیتر این موضوع کمک چندانی به ما نمی کنه🤗