- Khi chuyển màu số nguyên 8 bit sang số thực dấu phẩy động, có sự khác biệt giữa cách tiêu chuẩn chia cho 255 và cách thay thế cộng bias 0.5 rồi chia cho 256
- Cách 255 ánh xạ số nguyên 0 thành 0.0 và 255 thành 1.0, nên dễ xử lý trực tiếp màu đen và màu trắng, đồng thời cũng khớp với cách GPU chuyển đổi UNORM sang float
- Cách 256 dùng
(img + 0.5) / 256.0để đặt mỗi giá trị vào giữa một khoảng, có thể giúp đơn giản hóa xử lý biên trong các tác vụ như dithering, nhưng vì 0 không còn là 0.0 nên logic xử lý bị ràng buộc vào đầu vào 8 bit - Cách 255 có hai khoảng ở biên chỉ rộng bằng một nửa các khoảng còn lại, nên nếu làm tròn lại một số ngẫu nhiên đều trong
[0, 1]về 8 bit thì 0 và 255 sẽ xuất hiện với tần suất bằng một nửa các giá trị khác, nhưng việc chuyển đổi ảnh qua lại trong thực tế vẫn hoạt động không mất mát - Nếu xử lý ảnh từ người khác thì chuẩn hóa bằng 255 là đáp án đúng; chỉ nên cân nhắc cách 256 khi bạn kiểm soát toàn bộ quá trình lưu và nạp ảnh
Thiết lập vấn đề
- Trong một chương trình nhận ảnh, chuyển sang số thực dấu phẩy động để xử lý rồi lưu lại thành màu 8 bit, điểm tranh luận là cách chuyển đổi giữa số nguyên và số thực
- Có hai cách tiếp cận
- Cách tiêu chuẩn (chia cho 255):
pixels = img / 255.0→ xử lý →output = np.trunc(result * 255 + 0.5) - Cách thay thế (chia cho 256):
pixels = (img + 0.5) / 256.0→ xử lý →output = np.trunc(result * 256) - Trong cả hai trường hợp, giá trị trước khi ép kiểu cuối cùng đều được giới hạn trong khoảng 0~255:
output.clip(0, 255).astype(np.uint8)
- Cách tiêu chuẩn (chia cho 255):
- Cách tiêu chuẩn ánh xạ số nguyên 0 thành 0.0 và 255 thành 1.0, giống với cách GPU chuyển đổi UNORM sang float
- Cách thay thế cộng thêm bias 0.5 nên số nguyên 0 được ánh xạ thành
0.5/256 = 0.001953125- Vì vậy nếu không biết hằng số này thì không thể phát hiện pixel đen
- Dù tính toán bằng số thực dấu phẩy động, logic vẫn bị buộc chặt vào đầu vào 8 bit
- Với cách tiêu chuẩn, luôn có thể giả định màu đen là 0.0
Những phản biện với 255.0
- Nếu vẽ cách tiêu chuẩn lên trục số, nó trông có phần kỳ lạ
-
Có các bin nhỏ hơn ở hai đầu
- Hai bin ở biên của công thức tiêu chuẩn nhô ra ngoài phạm vi [0,1], tạo thành một dạng "kéo giãn" phạm vi
- Khi chuyển từ số thực dấu phẩy động trở lại số nguyên, độ rộng của hai bin ở biên chỉ bằng một nửa các bin khác
- Điều này khiến thuật toán "khó" tạo ra các giá trị cực trị hơn
- Nếu tạo nhiễu đều trong [0,1] rồi làm tròn bằng công thức tiêu chuẩn, giá trị 0 và 255 chỉ xuất hiện với tần suất bằng một nửa các số nguyên khác
- Có thể xác nhận điều này qua histogram của 1 triệu số ngẫu nhiên phân bố đều: bin của 0 và 255 chỉ cao bằng một nửa các bin còn lại
- Tuy vậy, rất khó nghĩ ra tình huống mà thiên lệch tránh cực trị này trở thành vấn đề thực tế
- Ảnh gốc vẫn chuyển đổi qua lại không mất mát (
uint8 → float → uint8) - Các kết quả hơi vượt ra ngoài 0.0 hoặc 1.0 vẫn được làm tròn về đúng bin, khiến phân bố đầu ra được cân bằng lại
- Ví dụ: nếu trong quá trình xử lý trừ đi 0.005 khỏi màu, thì với cách tiêu chuẩn màu đen sẽ xuống dưới 0, còn với cách thay thế vẫn dương, nhưng cả hai cách cuối cùng đều cho ra số nguyên 0
- Ảnh gốc vẫn chuyển đổi qua lại không mất mát (
-
Tính không chính xác
- Các giá trị số thực dấu phẩy động của cách tiêu chuẩn không chính xác tuyệt đối; ví dụ
128/255.0 ≈ 0.501961trong khi128/256.0 = 0.5 - Sai số làm tròn khiến khoảng cách giữa các giá trị số thực dao động rất nhỏ, nhưng sai số cực nhỏ nên không phải vấn đề thực tế
- Số thực 32 bit có mantissa 23 bit, nên sai số chỉ ở mức bit thấp nhất, nhỏ hơn
2⁻²³ - Sai số tương đối 0.00001% là vô nghĩa ngay cả trong xử lý ảnh tinh vi; tính không chính xác này là vấn đề thẩm mỹ hơn là kỹ thuật
- Số thực 32 bit có mantissa 23 bit, nên sai số chỉ ở mức bit thấp nhất, nhỏ hơn
- Các giá trị số thực dấu phẩy động của cách tiêu chuẩn không chính xác tuyệt đối; ví dụ
-
Các giá trị không thuộc dải số nguyên
- Cách thay thế đặt mỗi giá trị số thực dấu phẩy động đúng vào trung điểm giữa hai số nguyên
- Vì không thể biết giá trị lượng tử hóa gốc nên lấy trung điểm của hai số nguyên liên tiếp là một thỏa hiệp hợp lý để ước lượng
- Có quan điểm cho rằng điều này thuận tiện hơn cho dithering (bài blog năm 2015 của Andrew Kesler, "Converting Color Depth")
- Có thể thêm nhiễu mà không phải lo các edge case
- Ngược lại, các giá trị cực trị khó xử lý của công thức tiêu chuẩn đòi hỏi thao tác cẩn thận để giữ phân bố nhiễu nhất quán
- Cách thay thế đặt mỗi giá trị số thực dấu phẩy động đúng vào trung điểm giữa hai số nguyên
Hai loại bộ lượng tử hóa
- Có thể xem hai cách tiếp cận này là hai loại bộ lượng tử hóa vô hướng đều (uniform scalar quantizer)
- Theo bài viết Wikipedia về lượng tử hóa, bộ lượng tử hóa đều cho dữ liệu đầu vào có dấu được chia thành hai loại
- mid-tread: ánh xạ 0 vào mức tái tạo giá trị 0 (phần mặt bậc thang)
- mid-riser: ánh xạ 0 vào ngưỡng phân loại giá trị 0 (phần thành đứng của bậc thang)
- Wikipedia trích dẫn bài báo năm 1977 của Allen Gresho, "Quantization"
- Công thức lượng tử hóa (L là số mức đầu ra, ví dụ 256)
- Bộ lượng tử hóa bậc thang mid-tread: mã hóa
k = trunc(xL + 0.5), giải mãyₖ = k/L - Bộ lượng tử hóa bậc thang mid-riser: mã hóa
k = trunc(xL), giải mãyₖ = (k+0.5)/L
- Bộ lượng tử hóa bậc thang mid-tread: mã hóa
- Áp dụng vào hai cách trên
- Công thức tiêu chuẩn = mid-tread (L=255)
- Công thức thay thế = mid-riser (L=256)
- Cách tiêu chuẩn là sự kết hợp giữa việc dùng mid-tread cho đầu vào không dấu và chọn mã L=255, nên không tối ưu cho đầu vào 8 bit
- Đây là lựa chọn vì tiện cho lập trình khi muốn ánh xạ hai đầu thành 0.0 và 1.0
-
Sai số lượng tử hóa cao hơn, nhưng thực tế thì không
- Nếu coi đây là một hệ thống mã hóa số thực phân bố đều x∈[0,1] thành số nguyên 8 bit rồi tái tạo lại thành số thực, thì công thức tiêu chuẩn đúng là lãng phí băng thông
- Dải biểu diễn của cách tiêu chuẩn là
[-0.5/255, 255.5/255], rộng hơn mức cần thiết cho đầu vào [0,1], làm tăng sai số tái tạo - Theo phép tính của người dùng StackOverflow Peter Mudrievskij, sai số tuyệt đối trung bình là
1/1020với mẫu số 255 và1/1024với mẫu số 256, vì vậy chia cho 256 về mặt lý thuyết chính xác hơn một chút
- Dải biểu diễn của cách tiêu chuẩn là
- Nhưng trong thực tế ta không làm kiểu tái tạo này
- Giả định ở đây là nạp ảnh RGB 8 bit, xử lý rồi lưu lại; khi lưu không thể kiểm soát cách lượng tử hóa, và thông tin đã mất thì mất vĩnh viễn
- Nếu ảnh đã được lưu bằng cách nhân rồi làm tròn theo công thức tiêu chuẩn, thì khi nạp lại dù chia cho 256 cũng không thể khôi phục thêm độ chính xác
- Lập luận về sai số tái tạo thấp hơn chỉ có ý nghĩa khi bạn kiểm soát cả quá trình lưu và nạp
- Nếu nạp ảnh của người khác bằng công thức thay thế thì thậm chí còn gây thêm sai số
- Phần lớn ảnh có khả năng đã được lượng tử hóa theo công thức tiêu chuẩn, nên giải mã bằng sai thang đo sẽ không chính xác về lý thuyết
- Trên thực tế, màu sắc không phải đại lượng đo tuyệt đối, nên điều này chủ yếu chỉ là xử lý trong một dải nhỏ hơn đôi chút với một offset nhỏ
- Không được trộn lẫn bước mã hóa và giải mã của hai bộ lượng tử hóa; đây là kiểu code hỏng rất dễ mắc
- Nếu coi đây là một hệ thống mã hóa số thực phân bố đều x∈[0,1] thành số nguyên 8 bit rồi tái tạo lại thành số thực, thì công thức tiêu chuẩn đúng là lãng phí băng thông
Kết luận
- Nếu bạn xử lý ảnh do người khác cung cấp thì nên chuẩn hóa giá trị RGB bằng 255
- Những lo ngại về giá trị số thực không chính xác hay sai số tái tạo trừu tượng không phải là lý do đủ tốt để chọn phương án thay thế
- Nếu bạn kiểm soát toàn bộ việc lưu và nạp ảnh, không cần ánh xạ 0 thành 0, và chấp nhận để code xử lý bị ràng buộc vào dải động 8 bit, thì có thể chia cho 256 để có thêm một chút độ chính xác
- Tuy nhiên cần lưu ý rằng đồng nghiệp có thể nạp ảnh bằng công thức tiêu chuẩn và phá hỏng kế hoạch của bạn
Các quan điểm khác
- Bài viết năm 2002 của Jonathan Blow bàn về bộ lượng tử hóa mid-riser và mid-tread mà không gọi tên trực tiếp; đây là nguồn gốc của ý tưởng sơ đồ minh họa
- Bài blog năm 2015 của Andrew Kesler ủng hộ công thức thay thế
- Tuy nhiên, đối tượng so sánh trong bài là công thức tiêu chuẩn không có bước làm tròn, nên phần lớn phân tích bị vô hiệu
2 bình luận
Ý kiến trên Hacker News
Việc giá trị màu thực sự có ý nghĩa gì thì với 8 bit cho mỗi thành phần thường không quá quan trọng. Sai số tạo ra do khác biệt giữa mẫu số 255 hay 256 là rất nhỏ, và để nhìn ra khác biệt đó thì phải có cảm nhận màu tốt, dí mắt rất gần màn hình, trong khi màn hình máy tính hay điện thoại cũng thường không được hiệu chuẩn
Nhưng nếu tạo tín hiệu VGA bằng vi điều khiển và chỉ có 8 chân xuất màu (đỏ 3, lục 3, lam 2) thì lại khá đau đầu. Khi đó giá trị màu chính là mức điện áp 0V~0.7V cần gửi tới màn hình VGA
Kênh lam được ánh xạ thành 0→0V, 1→0.23V, 2→0.47V, 3→0.7V, còn đỏ/lục là 0→0V, 1→0.1V, …, 7→0.7V. Nếu bỏ hai đầu mút thì điện áp lam không khớp chút nào với điện áp đỏ/lục, nên không thể thấy màu xám thuần; ngay cả màu gần nhất cũng hơi ngả xanh lam hoặc vàng tùy theo hướng lệch
Hơn nữa, gần như mọi dải chuyển sắc có trộn lam với các kênh khác cũng trông bị lệch. Ví dụ, các màu gần nhất trên đường từ đỏ thuần tới trắng thuần sẽ hơi giống màu cam hoặc tím
Có mã cho Raspberry Pi Pico 2 xuất VGA màu 8 bit với framebuffer kép 320x240 ở đây: https://github.com/moefh/pico-vga-8bit-demo
Như vậy khác biệt giữa giá trị nhỏ và lớn sẽ nổi bật hơn nhiều: 2^2.2 = 4.595, 255^2.2 = 196,964.699
Nếu thay đổi ở 30Hz thì có lẽ con người khó phân biệt giữa hơi xanh lam và hơi vàng
Lập luận ủng hộ 255 có thể thấy rõ qua trường hợp cực đoan là ảnh đen trắng. Với một bit đơn, 0 là đen và 1 là trắng
Khá rõ ràng là 0 phải ánh xạ tới 0.0 và 1 phải ánh xạ tới 1.0. Vì đó là đen trắng, chứ không phải xám sáng (0.25) và xám tối (0.75). Tức là ảnh đen trắng được chuẩn hóa theo 1 chứ không phải 2
Với 2 bit thì thường là 0=đen, 1=xám sáng, 2=xám tối, 3=trắng, nên ánh xạ thành 0.0, 0.33, 0.66, 1.0 là tự nhiên hơn. Đen phải là đen, trắng phải là trắng, và khoảng cách cũng phải đều, nên chuẩn hóa theo 3
Kéo logic này lên 8 bit thì sẽ thành chuẩn hóa theo 255. Dù ở 8 bit khác biệt là rất nhỏ, đen vẫn phải là 0.0 và trắng vẫn phải là 1.0
Cách còn lại là dùng chuẩn hóa 256 cho 8 bit sẽ khiến dải đầu ra thay đổi theo số bit. 1 bit sẽ thành [0.25, 0.75], 2 bit sẽ thành [0.125, 0.875]. Điều người ta thường muốn là số bit tăng thì sắc độ tăng lên, chứ không phải độ tương phản thay đổi
Đây thực sự là bài viết khiến người ta phải suy nghĩ, và cá nhân tôi cũng phải xem lại những giả định mình từng có
Nhìn từ nền tảng kỹ thuật điện, tôi khó đồng ý với cách bài viết đưa ra “hai loại bộ lượng tử hóa”. Về mặt toán học thì chặt chẽ, nhưng không phải mô tả dựa trên hệ thống thực tế
ADC luôn có độ bất định lượng tử hóa ±1/2 LSB về bản chất. Đặc tính truyền luôn là lấy mẫu mid-tread, ít nhất tôi chưa từng thấy phản ví dụ nào. Điều này đúng cả với ADC lưỡng cực lẫn ADC đơn cực
Mã thấp nhất là tham chiếu điện áp âm và mã cao nhất là tham chiếu điện áp dương. Đồ thị đặc tính truyền cho thấy giống như trong bài, khoảng cao nhất/thấp nhất thực chất có bề rộng 1/2 LSB
Trong hệ đơn cực thì không thể biểu diễn chính xác điện áp ở giữa, nói cách khác sẽ phát sinh vấn đề màu xám. Trong hệ lưỡng cực thì 0V là giá trị N/2 của mid-tread, nhưng điều đó không có nghĩa là có “256 khoảng”
Vì thế tôi vẫn sẽ dùng (VREF+ - VREF-) * k / (2^N - 1). Tức là tôi đồng ý với chuẩn hóa 255. Xét cho cùng thì đây cũng giống lỗi đếm cọc hàng rào: có N giá trị nhưng chỉ có N-1 khoảng. Nếu số khoảng ít hơn số giá trị thì phải chia một khoảng cho hai giá trị, nên ở điểm cuối sẽ có khoảng 1/2 LSB
Chuyển tiếp từ 126 sang 127 xảy ra tại điểm cách toàn dải dương 1.5 LSB. Chênh lệch 1 LSB có nghĩa là sai khác 1/128=0.00781V chứ không phải 2/255=0.00784V
Nhưng trên thực tế, nếu điện áp và độ bất định mới là thứ quan trọng thì kiểu khác biệt này hầu như chẳng đáng kể. Điện áp tham chiếu có thiên lệch và cũng có lỗi tuyến tính. 1 LSB không khớp chính xác với cả 1/128 lẫn 2/255, và sẽ cần các tham số để hiệu chuẩn
Điều này khá giống khác biệt giữa mẫu đặt tại nút và mẫu đặt tại tâm ô trong tính toán khoa học, nhưng ở dạng một chiều. Cần xác định xem giá trị nằm ở giữa khoảng (hoặc giữa tam giác/tứ diện) hay ở biên khoảng (hoặc tại đỉnh tam giác/tứ diện)
Trong tính toán khoa học, bắt đầu xử lý dữ liệu khi còn chưa biết phải diễn giải giá trị thế nào là điều vô nghĩa. Trong xử lý tín hiệu âm thanh cũng vậy: nếu chỉ nhận được luồng số nguyên, muốn tính toán trên tín hiệu gốc thì phải biết các số nguyên đó được biểu diễn theo ý đồ nào, ví dụ mã hóa mu-law hay tuyến tính. Người ta kỳ vọng metadata đi kèm giá trị sẽ cho câu trả lời đó
Nhưng với giá trị pixel 8 bit, nếu không có metadata đúng nghĩa trong định dạng tệp để truyền tải ý đồ biểu diễn thì sẽ rơi vào trạng thái mơ hồ, và không có đáp án đúng. Như tác giả nói, không thể trách ai đó chọn cách cho kết quả tốt hơn với mục đích của họ, nhưng vẫn có thể nói rằng bit không có ngữ cảnh thì ý nghĩa sẽ bị méo đi
Đại khái là thế này: Digital Number DN=0 được giữ lại làm giá trị “NO_DATA”, còn khi DN nằm trong khoảng [1; 1;215-1] thì giá trị phản xạ L2A SR là L2A_SRi = (L2A_DNi + BOA_ADD_OFFSETi) / QUANTIFICATION_VALUE
https://sentiwiki.copernicus.eu/web/s2-products
Ở đây có một lỗi là giả định có 256 mức từ 0 đến 255. Thực ra có 256 giá trị có thể biểu diễn bằng 8 bit, còn số khoảng cách từ 0 (đen) đến 255 (trắng thuần) là 255
Vì vậy chia cho 255 không phải là vấn đề. Tất nhiên 128 không phải là màu xám chính giữa một cách chính xác, và các giá trị 8 bit được lượng tử hóa từ 0~255 hầu như luôn nằm trong sRGB chứ không phải không gian cảm nhận tuyến tính
Sự nhầm lẫn tương tự cũng xuất hiện khi xử lý vị trí lấy mẫu trong các API hiện đại. Đó là vì vị trí được chỉ định bằng tọa độ chứ không phải theo tâm pixel
Nếu nhìn theo đại số thì câu trả lời rõ ràng là f(x) -> [0, 255]
Nếu f(n * 0) == n * f(0) không đúng thì sẽ xảy ra những điều kỳ lạ. Ví dụ, nếu f(x) -> [0, 255] thì f(0) + f(0) + f(0) = 0 + 0 + 0 = 0 = f(0)
Ngược lại, nếu f(x) -> [0.5/8, 7.5/8] thì f(0) + f(0) + f(0) = 0.5/8 + 0.5/8 + 0.5/8 = 1.5/8 != f(0)
Nếu chọn cách sau thì không thể kỳ vọng phép tính làm ở phía x và phép tính làm ở phía f(x) sẽ khớp nhau. Tức là tương ứng đại số bị phá vỡ
Tôi muốn ủng hộ lời giải +0.5. Thứ nhất là tôi không thích các khoảng ở rìa chỉ có nửa độ rộng, thứ hai là biểu diễn dựa trên 255 thường là ảnh SDR chứ không phải HDR
Giá trị RGB biểu diễn độ chói tương ứng với một trạng thái thích nghi nào đó, và “0” trong cảnh ban ngày không phải là “độ chói 0”. Nó chỉ cỡ khoảng 0.001 lần điểm sáng nhất, và số photon vẫn là hàng triệu, tức là nhiều hơn 0 rất xa
Theo một nghĩa nào đó, mắt trải nghiệm độ tương phản như một thang trượt, và không có số 0 tuyệt đối trong hệ thống. Ví dụ, các hệ thống phát sóng trong lịch sử đã dùng 16~235 cho dải độ chói SDR. Tôi cho rằng lập luận “nhất thiết phải có 0” tạo ra thiên lệch, và trong đa số trường hợp thì không cần 0
Ngoài ra, khá nhiều workflow xử lý ảnh và compositing, đúng hay sai, đều giả định rằng 0 nghĩa là 0. Vì thế người ta thường coi 0u trong 8 bit ánh xạ thành 0.0f, còn 255 thành 1.0f. Nếu giá trị 0 trong mask hay alpha lớn hơn 0.0 một chút, sẽ xuất hiện artifact khi đâu đó trong code dùng ngưỡng cứng 0.0 để mask các phép toán khác. Ngược lại, nếu trong alpha mà 255 không còn là 1.0f nữa, thì sau phép premultiply đối tượng sẽ hơi trong suốt một chút
Điều tương tự cũng có thể xảy ra khi do +0.5 mà 254 trở thành 1.0f trong masking
Điều cốt lõi không phải là có biểu diễn 0 photon hay không, mà là có tối đa hóa thông tin lưu trong 1 byte hay không. Lý tưởng nhất là không nên dùng byte 0 ít đi, cũng không nên cộng bias vào dữ liệu đáng lẽ thuộc bucket thứ 0. Dù là không gian màu từ sáng đến rất sáng, mọi byte vẫn nên biểu diễn các lát có cùng kích thước của dải độ sáng
Nếu thước có tới 12 inch, thì phải chuẩn hóa theo độ dài L chứ không phải theo số điểm là 13 trên thước
>> 8nhanh hơn nhiềuĐây là một bài viết thú vị vì nó chạm tới chủ đề mà tôi đã không nghĩ đến suốt một thời gian. Nó làm tôi nhớ tới những lúc trong phát triển game, logic game dùng toán dấu phẩy động nhưng pixel art thì phải được vẽ theo tọa độ số nguyên
Ở vài chỗ tôi đã dùng cách tương tự +0.5 để làm mọi thứ trông bớt kỳ quặc hơn. Đặc biệt là khi có camera di chuyển, và camera cũng phải bị cắt về lưới
Bài viết năm 2002 của Jonathan Blow được link bên dưới [1] cũng rất thú vị. Phần trực quan hóa trong bài đầu tiên giúp ích rất nhiều khi đi sâu hơn
[1] https://web.archive.org/web/20240706043551/https://number-no...
Ý kiến trên Lobste.rs
Nếu thấy không trực quan thì hãy nhìn vào trường hợp suy biến 2 bit. Khi các giá trị nguyên khả dĩ chỉ là 0, 1, 2, 3, nếu tính toàn bộ phép chuyển đổi từ số nguyên sang số thực dấu phẩy động thì để tránh hành vi kỳ quặc như đen/trắng không còn là đen/trắng hoặc khoảng cách rõ ràng không đều, ta sẽ có 0.0, 0.33..., 0.66..., 1.0
Vì vậy phép chuyển ngược sẽ là nhân với 3, chứ không phải 4(2^2)
Chuyển ngược cần lượng tử hóa (làm tròn), và chính điểm này mới là thứ phá vỡ tính đối xứng
Nếu tạo một dải chuyển màu số thực đều trong khoảng 0..=1 rồi lượng tử hóa về 0, 1, 2, 3, bạn sẽ thấy nhân 3 cho kết quả không đều.
round()sau khi ×3 sẽ làm 1 và 2 bị biểu diễn quá mức, cònfloorhoặcceilsau khi ×3 thì lại gộp 0 hoặc 3 thành kiểu điểm kỳ dị, khiến dải màu trông như chỉ dùng 3 trong 4 màuLogic
/3và×3có vẻ ổn khi chuyển đổi qua lại các con số chính xác, nhưng các giá trị trung gian bị ảnh hưởng mạnh bởi lựa chọn làm tròn, và điều đó trở nên quan trọng ngay khi bắt đầu xử lý dữ liệuTỷ lệ số nguyên chỉ trở nên đồng đều khi nhân với (4-ε) rồi lấy floor, tức tương đương với ×4,
floor(),clamp(). Nó có cảm giác như một lỗi kỳ quặc lệch 1 hoặc lệch ε, nhưng về mặt trực giác lại là lời giải trông hợp lý nhấtVới tôi thì đáp án luôn “rõ ràng” là [0.0..255.0], nhưng có lẽ không phải ai cũng thấy hiển nhiên như vậy
Bài viết nói rằng các vùng “cực trị” chỉ có một nửa dung lượng so với các vùng khác, và tôi cũng không nghĩ cách đặt vấn đề này là đúng
Nếu không tồn tại giá trị nào ngoài [0..1], thì việc nó trông như một vùng hẹp là sản phẩm của cách render. Nó chỉ được render hẹp hơn vì bạn đã cắt bucket dựa trên hiểu biết rằng không có giá trị nào ngoài phạm vi đó
Ngược lại, nếu có tồn tại giá trị ngoài [0..1], thì phạm vi đó là vô hạn. Bài viết thừa nhận trường hợp sau nhưng lại không thừa nhận trường hợp trước
Một khi chấp nhận điều thứ nhất thì cách hoạt động đúng có vẻ khá rõ ràng, nhưng việc bài này xuất hiện tự nó cũng có nghĩa đây không hẳn là một vấn đề “rõ ràng” theo nghĩa khách quan :D
Nếu 0..<1 đi về số nguyên 0, còn 254>..255.0 đi về số nguyên 255, thì 128 sẽ bị nuốt mất. Có lẽ bạn muốn 127.5..128.5 đi về 128, nhưng vậy thì các nửa khoảng này phải đi đâu?
Nếu dịch toàn bộ đi một chút để khớp 128, thì 0..0.99609375 sẽ ánh xạ về số nguyên 0
round()Có vẻ cách đó mang lại cảm giác khá tự nhiên cho nhiều người, nên nhờ tính đơn giản mà nó trở thành tiêu chuẩn
pngcrush. Hay ý bạn là nội dung ảnh có gì đó không đúng?