Support float values where the zeros change the exponent

When underflow is detected in float parsing, WGSL returns `0.0`.
Currently, if the number of `0`s following a `.` shifts the exponent
from a positive to a negative we were not correctly identify the
underflow and returning 0. This CL updates the float parsing to detect
undeflow in this case and return `0.0` as expected.

Bug: tint:1863
Change-Id: I164063cebf70f825fdf2753dff8a4f016939c38e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/124341
Commit-Queue: David Neto <dneto@google.com>
Reviewed-by: David Neto <dneto@google.com>
Auto-Submit: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/reader/wgsl/lexer.cc b/src/tint/reader/wgsl/lexer.cc
index 91c2993..f795053 100644
--- a/src/tint/reader/wgsl/lexer.cc
+++ b/src/tint/reader/wgsl/lexer.cc
@@ -331,18 +331,32 @@
     auto source = begin_source();
     bool has_mantissa_digits = false;
 
+    std::optional<size_t> first_significant_digit_position;
     while (end < length() && is_digit(at(end))) {
+        if (!first_significant_digit_position.has_value() && (at(end) != '0')) {
+            first_significant_digit_position = end;
+        }
+
         has_mantissa_digits = true;
         end++;
     }
 
-    bool has_point = false;
+    std::optional<size_t> dot_position;
     if (end < length() && matches(end, '.')) {
-        has_point = true;
+        dot_position = end;
         end++;
     }
 
+    size_t zeros_before_digit = 0;
     while (end < length() && is_digit(at(end))) {
+        if (!first_significant_digit_position.has_value()) {
+            if (at(end) == '0') {
+                zeros_before_digit += 1;
+            } else {
+                first_significant_digit_position = end;
+            }
+        }
+
         has_mantissa_digits = true;
         end++;
     }
@@ -352,7 +366,7 @@
     }
 
     // Parse the exponent if one exists
-    bool has_exponent = false;
+    std::optional<size_t> exponent_value_position;
     bool negative_exponent = false;
     if (end < length() && (matches(end, 'e') || matches(end, 'E'))) {
         end++;
@@ -360,14 +374,16 @@
             negative_exponent = matches(end, '-');
             end++;
         }
+        exponent_value_position = end;
 
+        bool has_digits = false;
         while (end < length() && isdigit(at(end))) {
-            has_exponent = true;
+            has_digits = true;
             end++;
         }
 
         // If an 'e' or 'E' was present, then the number part must also be present.
-        if (!has_exponent) {
+        if (!has_digits) {
             const auto str = std::string{substr(start, end - start)};
             return {Token::Type::kError, source,
                     "incomplete exponent for floating point literal: " + str};
@@ -382,7 +398,8 @@
         has_h_suffix = true;
     }
 
-    if (!has_point && !has_exponent && !has_f_suffix && !has_h_suffix) {
+    if (!dot_position.has_value() && !exponent_value_position.has_value() && !has_f_suffix &&
+        !has_h_suffix) {
         // If it only has digits then it's an integer.
         return {};
     }
@@ -399,10 +416,32 @@
     auto ret = absl::from_chars(&at(start), end_ptr, value);
     bool overflow = ret.ec != std::errc();
 
-    // The provided value did not fit in a double and has a negative exponent, so treat it as a 0.
-    if (negative_exponent && ret.ec == std::errc::result_out_of_range) {
-        overflow = false;
-        value = 0.0;
+    // Value didn't fit in a double, check for underflow as that is 0.0 in WGSL and not an error.
+    if (ret.ec == std::errc::result_out_of_range) {
+        // The exponent is negative, so treat as underflow
+        if (negative_exponent) {
+            overflow = false;
+            value = 0.0;
+        } else if (dot_position.has_value() && first_significant_digit_position.has_value() &&
+                   first_significant_digit_position.value() > dot_position.value()) {
+            // Parse the exponent from the float if provided
+            size_t exp_value = 0;
+            bool exp_conversion_succeeded = true;
+            if (exponent_value_position.has_value()) {
+                auto exp_end_ptr = end_ptr - (has_f_suffix || has_h_suffix ? 1 : 0);
+                auto exp_ret = std::from_chars(&at(exponent_value_position.value()), exp_end_ptr,
+                                               exp_value, 10);
+
+                if (exp_ret.ec != std::errc{}) {
+                    exp_conversion_succeeded = false;
+                }
+            }
+            // If the exponent has gone negative, then this is an underflow case
+            if (exp_conversion_succeeded && exp_value < zeros_before_digit) {
+                overflow = false;
+                value = 0.0;
+            }
+        }
     }
 
     TINT_ASSERT(Reader, end_ptr == ret.ptr);
diff --git a/src/tint/reader/wgsl/lexer_test.cc b/src/tint/reader/wgsl/lexer_test.cc
index a244273..8d49bf8 100644
--- a/src/tint/reader/wgsl/lexer_test.cc
+++ b/src/tint/reader/wgsl/lexer_test.cc
@@ -492,7 +492,56 @@
                              // Quantization
                              FloatData{"3.141592653589793", 3.141592653589793},  // no quantization
                              FloatData{"3.141592653589793f", 3.1415927410125732},  // f32 quantized
-                             FloatData{"3.141592653589793h", 3.140625}             // f16 quantized
+                             FloatData{"3.141592653589793h", 3.140625},            // f16 quantized
+
+                             // https://bugs.chromium.org/p/tint/issues/detail?id=1863
+                             FloatData{"0."
+                                       "00000000000000000000000000000000000000000000000000"  //  50
+                                       "00000000000000000000000000000000000000000000000000"  // 100
+                                       "00000000000000000000000000000000000000000000000000"  // 150
+                                       "00000000000000000000000000000000000000000000000000"  // 200
+                                       "00000000000000000000000000000000000000000000000000"  // 250
+                                       "00000000000000000000000000000000000000000000000000"  // 300
+                                       "00000000000000000000000000000000000000000000000000"  // 350
+                                       "1e+0",
+                                       0.0},
+                             FloatData{"0."
+                                       "00000000000000000000000000000000000000000000000000"  //  50
+                                       "00000000000000000000000000000000000000000000000000"  // 100
+                                       "00000000000000000000000000000000000000000000000000"  // 150
+                                       "00000000000000000000000000000000000000000000000000"  // 200
+                                       "00000000000000000000000000000000000000000000000000"  // 250
+                                       "00000000000000000000000000000000000000000000000000"  // 300
+                                       "00000000000000000000000000000000000000000000000000"  // 350
+                                       "1e+10",
+                                       0.0},
+                             FloatData{"0."
+                                       "00000000000000000000000000000000000000000000000000"  //  50
+                                       "00000000000000000000000000000000000000000000000000"  // 100
+                                       "00000000000000000000000000000000000000000000000000"  // 150
+                                       "00000000000000000000000000000000000000000000000000"  // 200
+                                       "00000000000000000000000000000000000000000000000000"  // 250
+                                       "00000000000000000000000000000000000000000000000000"  // 300
+                                       "00000000000000000000000000000000000000000000000000"  // 350
+                                       "1e+300",
+                                       1e-51},
+                             FloatData{"0."
+                                       "00000000000000000000000000000000000000000000000000"  //  50
+                                       "00000000000000000000000000000000000000000000000000"  // 100
+                                       "00000000000000000000000000000000000000000000000000"  // 150
+                                       "00000000000000000000000000000000000000000000000000"  // 200
+                                       "00000000000000000000000000000000000000000000000000"  // 250
+                                       "00000000000000000000000000000000000000000000000000"  // 300
+                                       "00000000000000000000000000000000000000000000000000"  // 350
+                                       "1",
+                                       0.0},
+
+                             FloatData{"1"
+                                       "00000000000000000000000000000000000000000000000000"  //  50
+                                       "00000000000000000000000000000000000000000000000000"  // 100
+                                       ".0e-350",
+                                       1e-250}
+
                              ));
 
 using FloatTest_Invalid = testing::TestWithParam<const char*>;