jubins commented on code in PR #59214:
URL: https://github.com/apache/spark/pull/59214#discussion_r4171645550


##########
python/pyspark/sql/variant_utils.py:
##########
@@ -413,10 +417,93 @@ def handle_array(value_pos_list: List[int]) -> str:
             if isinstance(value, bytes):
                 # decoding simply converts byte array to string
                 return '"' + base64.b64encode(value).decode("utf-8") + '"'
-            if isinstance(value, (datetime.date, datetime.datetime)):
+            if isinstance(value, float):
+                return cls._float_to_json(value, is_float32)
+            if isinstance(value, decimal.Decimal):
+                return cls._decimal_to_json(value)
+            if isinstance(value, datetime.datetime):
+                return '"' + cls._timestamp_to_json(value) + '"'
+            if isinstance(value, datetime.date):
                 return '"' + str(value) + '"'
             return str(value)
 
+    # The helpers below format scalars the same way as the JVM 
`Variant.toJson`, so that
+    # `VariantVal.toJson` and the `to_json` SQL function produce the same 
output.
+
+    @classmethod
+    def _float_to_json(cls, f: float, is_float32: bool) -> str:
+        """
+        Formats a double or float like Java's `Double.toString` and 
`Float.toString`. Non-finite
+        values are quoted because they are not valid JSON numbers.
+        """
+        if math.isnan(f):
+            return '"NaN"'
+        if math.isinf(f):
+            return '"Infinity"' if f > 0 else '"-Infinity"'
+
+        def round_trips(s: str) -> bool:
+            if not is_float32:
+                return float(s) == f
+            try:
+                return struct.pack("<f", float(s)) == struct.pack("<f", f)
+            except OverflowError:
+                return False
+
+        # Like Java, pick the shortest decimal that round-trips, using at 
least 2 significant
+        # digits (e.g. Double.MIN_VALUE is "4.9E-324", not "5.0E-324"), and 
the one closest to
+        # `f` among those of that length.
+        for precision in range(2, 18):
+            s = "%.*e" % (precision - 1, f)
+            if round_trips(s):
+                break
+            # When `f` is a power of two, the values that round to it extend 
only half as far
+            # toward zero as away from it. So the closest decimal of this 
length can fail to
+            # round-trip while the next one away from zero still does (e.g. 
2^-24 is
+            # "5.960464477539063E-8", not "5.9604644775390625E-8").
+            closest = decimal.Decimal(s)
+            exponent = closest.as_tuple().exponent
+            assert isinstance(exponent, int)
+            last_digit = decimal.Decimal(1).scaleb(exponent)
+            s = str(closest + last_digit if f > 0 else closest - last_digit)

Review Comment:
   added negative double and float vectors.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to