Browse Source

Merge pull request #98750 from aaronfranke/grisu

Use Grisu2 algorithm in `String::num_scientific` to fix serializing
Thaddeus Crews 3 months ago
parent
commit
6258a3e224

+ 6 - 0
COPYRIGHT.txt

@@ -293,6 +293,12 @@ Comment: Graphite engine
 Copyright: 2010, SIL International
 License: Expat
 
+Files: thirdparty/grisu2/grisu2.h
+Comment: Grisu2 float serialization algorithm
+Copyright: 2009, Florian Loitsch
+ 2018-2023, The simdjson authors
+License: Expat and Apache
+
 Files: thirdparty/harfbuzz/*
 Comment: HarfBuzz text shaping library
 Copyright: 2010-2022, Google, Inc.

+ 16 - 2
core/doc_data.cpp

@@ -31,10 +31,24 @@
 #include "doc_data.h"
 
 String DocData::get_default_value_string(const Variant &p_value) {
-	if (p_value.get_type() == Variant::ARRAY) {
+	const Variant::Type type = p_value.get_type();
+	if (type == Variant::ARRAY) {
 		return Variant(Array(p_value, 0, StringName(), Variant())).get_construct_string().replace_char('\n', ' ');
-	} else if (p_value.get_type() == Variant::DICTIONARY) {
+	} else if (type == Variant::DICTIONARY) {
 		return Variant(Dictionary(p_value, 0, StringName(), Variant(), 0, StringName(), Variant())).get_construct_string().replace_char('\n', ' ');
+	} else if (type == Variant::INT) {
+		return itos(p_value);
+	} else if (type == Variant::FLOAT) {
+		// Since some values are 32-bit internally, use 32-bit for all
+		// documentation values to avoid garbage digits at the end.
+		const String s = String::num_scientific((float)p_value);
+		// Use float literals for floats in the documentation for clarity.
+		if (s != "inf" && s != "-inf" && s != "nan") {
+			if (!s.contains_char('.') && !s.contains_char('e')) {
+				return s + ".0";
+			}
+		}
+		return s;
 	} else {
 		return p_value.get_construct_string().replace_char('\n', ' ');
 	}

+ 13 - 21
core/string/ustring.cpp

@@ -43,6 +43,8 @@
 #include "core/variant/variant.h"
 #include "core/version_generated.gen.h"
 
+#include "thirdparty/grisu2/grisu2.h"
+
 #ifdef _MSC_VER
 #define _CRT_SECURE_NO_WARNINGS // to disable build-time warning which suggested to use strcpy_s instead strcpy
 #endif
@@ -1656,28 +1658,18 @@ String String::num_scientific(double p_num) {
 	if (Math::is_nan(p_num) || Math::is_inf(p_num)) {
 		return num(p_num, 0);
 	}
+	char buffer[256];
+	char *last = grisu2::to_chars(buffer, p_num);
+	return String::ascii(Span(buffer, last - buffer));
+}
 
-	char buf[256];
-
-#if defined(__GNUC__) || defined(_MSC_VER)
-
-#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT)
-	// MinGW requires _set_output_format() to conform to C99 output for printf
-	unsigned int old_exponent_format = _set_output_format(_TWO_DIGIT_EXPONENT);
-#endif
-	snprintf(buf, 256, "%lg", p_num);
-
-#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT)
-	_set_output_format(old_exponent_format);
-#endif
-
-#else
-	sprintf(buf, "%.16lg", p_num);
-#endif
-
-	buf[255] = 0;
-
-	return buf;
+String String::num_scientific(float p_num) {
+	if (Math::is_nan(p_num) || Math::is_inf(p_num)) {
+		return num(p_num, 0);
+	}
+	char buffer[256];
+	char *last = grisu2::to_chars(buffer, p_num);
+	return String::ascii(Span(buffer, last - buffer));
 }
 
 String String::md5(const uint8_t *p_md5) {

+ 1 - 0
core/string/ustring.h

@@ -446,6 +446,7 @@ public:
 	String unquote() const;
 	static String num(double p_num, int p_decimals = -1);
 	static String num_scientific(double p_num);
+	static String num_scientific(float p_num);
 	static String num_real(double p_num, bool p_trailing = true);
 	static String num_real(float p_num, bool p_trailing = true);
 	static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false);

+ 11 - 1
core/variant/variant_call.cpp

@@ -1697,6 +1697,16 @@ StringName Variant::get_enum_for_enumeration(Variant::Type p_type, const StringN
 	register_builtin_method<Method_##m_type##_##m_method>(sarray(), m_default_args);
 #endif // DEBUG_ENABLED
 
+#ifdef DEBUG_ENABLED
+#define bind_static_methodv(m_type, m_name, m_method, m_arg_names, m_default_args) \
+	STATIC_METHOD_CLASS(m_type, m_name, m_method);                                 \
+	register_builtin_method<Method_##m_type##_##m_name>(m_arg_names, m_default_args);
+#else
+#define bind_static_methodv(m_type, m_name, m_method, m_arg_names, m_default_args) \
+	STATIC_METHOD_CLASS(m_type, m_name, m_method);                                 \
+	register_builtin_method<Method_##m_type##_##m_name>(sarray(), m_default_args);
+#endif
+
 #ifdef DEBUG_ENABLED
 #define bind_methodv(m_type, m_name, m_method, m_arg_names, m_default_args) \
 	METHOD_CLASS(m_type, m_name, m_method);                                 \
@@ -1882,7 +1892,7 @@ static void _register_variant_builtin_methods_string() {
 	bind_string_method(to_multibyte_char_buffer, sarray("encoding"), varray(String()));
 	bind_string_method(hex_decode, sarray(), varray());
 
-	bind_static_method(String, num_scientific, sarray("number"), varray());
+	bind_static_methodv(String, num_scientific, static_cast<String (*)(double)>(&String::num_scientific), sarray("number"), varray());
 	bind_static_method(String, num, sarray("number", "decimals"), varray(-1));
 	bind_static_method(String, num_int64, sarray("number", "base", "capitalize_hex"), varray(10, false));
 	bind_static_method(String, num_uint64, sarray("number", "base", "capitalize_hex"), varray(10, false));

+ 30 - 16
core/variant/variant_parser.cpp

@@ -1934,22 +1934,30 @@ Error VariantParser::parse(Stream *p_stream, Variant &r_ret, String &r_err_str,
 //////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////
 
+// These two functions serialize floats or doubles using num_scientific to ensure
+// it can be read back in the same way (except collapsing -0 to 0, and NaN values).
+static String rtos_fix(float p_value, bool p_compat) {
+	if (p_value == 0.0f) {
+		return "0"; // Avoid negative zero (-0) being written, which may annoy git, svn, etc. for changes when they don't exist.
+	} else if (p_compat) {
+		// Write old inf_neg for compatibility.
+		if (std::isinf(p_value) && p_value < 0.0f) {
+			return "inf_neg";
+		}
+	}
+	return String::num_scientific(p_value);
+}
+
 static String rtos_fix(double p_value, bool p_compat) {
 	if (p_value == 0.0) {
-		return "0"; //avoid negative zero (-0) being written, which may annoy git, svn, etc. for changes when they don't exist.
-	} else if (std::isnan(p_value)) {
-		return "nan";
-	} else if (std::isinf(p_value)) {
-		if (p_value > 0) {
-			return "inf";
-		} else if (p_compat) {
+		return "0"; // Avoid negative zero (-0) being written, which may annoy git, svn, etc. for changes when they don't exist.
+	} else if (p_compat) {
+		// Write old inf_neg for compatibility.
+		if (std::isinf(p_value) && p_value < 0.0) {
 			return "inf_neg";
-		} else {
-			return "-inf";
 		}
-	} else {
-		return rtoss(p_value);
 	}
+	return String::num_scientific(p_value);
 }
 
 Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_string_func, void *p_store_string_ud, EncodeResourceFunc p_encode_res_func, void *p_encode_res_ud, int p_recursion_count, bool p_compat) {
@@ -1964,11 +1972,17 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
 			p_store_string_func(p_store_string_ud, itos(p_variant.operator int64_t()));
 		} break;
 		case Variant::FLOAT: {
-			String s = rtos_fix(p_variant.operator double(), p_compat);
-			if (s != "inf" && s != "-inf" && s != "nan") {
-				if (!s.contains_char('.') && !s.contains_char('e') && !s.contains_char('E')) {
-					s += ".0";
-				}
+			const double value = p_variant.operator double();
+			String s;
+			// Hack to avoid garbage digits when the underlying float is 32-bit.
+			if ((double)(float)value == value) {
+				s = rtos_fix((float)value, p_compat);
+			} else {
+				s = rtos_fix(value, p_compat);
+			}
+			// Append ".0" to floats to ensure they are float literals.
+			if (s != "inf" && s != "-inf" && s != "nan" && !s.contains_char('.') && !s.contains_char('e') && !s.contains_char('E')) {
+				s += ".0";
 			}
 			p_store_string_func(p_store_string_ud, s);
 		} break;

+ 1 - 1
doc/classes/Animation.xml

@@ -689,7 +689,7 @@
 		<member name="loop_mode" type="int" setter="set_loop_mode" getter="get_loop_mode" enum="Animation.LoopMode" default="0">
 			Determines the behavior of both ends of the animation timeline during animation playback. This is used for correct interpolation of animation cycles, and for hinting the player that it must restart the animation.
 		</member>
-		<member name="step" type="float" setter="set_step" getter="get_step" default="0.0333333">
+		<member name="step" type="float" setter="set_step" getter="get_step" default="0.033333335">
 			The animation step value.
 		</member>
 	</members>

+ 2 - 2
doc/classes/CharacterBody2D.xml

@@ -154,7 +154,7 @@
 			If [code]false[/code] (by default), the body will move faster on downward slopes and slower on upward slopes.
 			If [code]true[/code], the body will always move at the same speed on the ground no matter the slope. Note that you need to use [member floor_snap_length] to stick along a downward slope at constant speed.
 		</member>
-		<member name="floor_max_angle" type="float" setter="set_floor_max_angle" getter="get_floor_max_angle" default="0.785398">
+		<member name="floor_max_angle" type="float" setter="set_floor_max_angle" getter="get_floor_max_angle" default="0.7853982">
 			Maximum angle (in radians) where a slope is still considered a floor (or a ceiling), rather than a wall, when calling [method move_and_slide]. The default value equals 45 degrees.
 		</member>
 		<member name="floor_snap_length" type="float" setter="set_floor_snap_length" getter="get_floor_snap_length" default="1.0">
@@ -195,7 +195,7 @@
 		<member name="velocity" type="Vector2" setter="set_velocity" getter="get_velocity" default="Vector2(0, 0)">
 			Current velocity vector in pixels per second, used and modified during calls to [method move_and_slide].
 		</member>
-		<member name="wall_min_slide_angle" type="float" setter="set_wall_min_slide_angle" getter="get_wall_min_slide_angle" default="0.261799">
+		<member name="wall_min_slide_angle" type="float" setter="set_wall_min_slide_angle" getter="get_wall_min_slide_angle" default="0.2617994">
 			Minimum angle (in radians) where the body is allowed to slide when it encounters a wall. The default value equals 15 degrees. This property only affects movement when [member motion_mode] is [constant MOTION_MODE_FLOATING].
 		</member>
 	</members>

+ 2 - 2
doc/classes/CharacterBody3D.xml

@@ -145,7 +145,7 @@
 			If [code]false[/code] (by default), the body will move faster on downward slopes and slower on upward slopes.
 			If [code]true[/code], the body will always move at the same speed on the ground no matter the slope. Note that you need to use [member floor_snap_length] to stick along a downward slope at constant speed.
 		</member>
-		<member name="floor_max_angle" type="float" setter="set_floor_max_angle" getter="get_floor_max_angle" default="0.785398">
+		<member name="floor_max_angle" type="float" setter="set_floor_max_angle" getter="get_floor_max_angle" default="0.7853982">
 			Maximum angle (in radians) where a slope is still considered a floor (or a ceiling), rather than a wall, when calling [method move_and_slide]. The default value equals 45 degrees.
 		</member>
 		<member name="floor_snap_length" type="float" setter="set_floor_snap_length" getter="get_floor_snap_length" default="0.1">
@@ -186,7 +186,7 @@
 		<member name="velocity" type="Vector3" setter="set_velocity" getter="get_velocity" default="Vector3(0, 0, 0)">
 			Current velocity vector (typically meters per second), used and modified during calls to [method move_and_slide].
 		</member>
-		<member name="wall_min_slide_angle" type="float" setter="set_wall_min_slide_angle" getter="get_wall_min_slide_angle" default="0.261799">
+		<member name="wall_min_slide_angle" type="float" setter="set_wall_min_slide_angle" getter="get_wall_min_slide_angle" default="0.2617994">
 			Minimum angle (in radians) where the body is allowed to slide when it encounters a wall. The default value equals 15 degrees. When [member motion_mode] is [constant MOTION_MODE_GROUNDED], it only affects movement if [member floor_block_on_wall] is [code]true[/code].
 		</member>
 	</members>

+ 131 - 131
doc/classes/Color.xml

@@ -538,343 +538,343 @@
 		</member>
 	</members>
 	<constants>
-		<constant name="ALICE_BLUE" value="Color(0.941176, 0.972549, 1, 1)">
+		<constant name="ALICE_BLUE" value="Color(0.9411765, 0.972549, 1, 1)">
 			Alice blue color.
 		</constant>
-		<constant name="ANTIQUE_WHITE" value="Color(0.980392, 0.921569, 0.843137, 1)">
+		<constant name="ANTIQUE_WHITE" value="Color(0.98039216, 0.92156863, 0.84313726, 1)">
 			Antique white color.
 		</constant>
 		<constant name="AQUA" value="Color(0, 1, 1, 1)">
 			Aqua color.
 		</constant>
-		<constant name="AQUAMARINE" value="Color(0.498039, 1, 0.831373, 1)">
+		<constant name="AQUAMARINE" value="Color(0.49803922, 1, 0.83137256, 1)">
 			Aquamarine color.
 		</constant>
-		<constant name="AZURE" value="Color(0.941176, 1, 1, 1)">
+		<constant name="AZURE" value="Color(0.9411765, 1, 1, 1)">
 			Azure color.
 		</constant>
-		<constant name="BEIGE" value="Color(0.960784, 0.960784, 0.862745, 1)">
+		<constant name="BEIGE" value="Color(0.9607843, 0.9607843, 0.8627451, 1)">
 			Beige color.
 		</constant>
-		<constant name="BISQUE" value="Color(1, 0.894118, 0.768627, 1)">
+		<constant name="BISQUE" value="Color(1, 0.89411765, 0.76862746, 1)">
 			Bisque color.
 		</constant>
 		<constant name="BLACK" value="Color(0, 0, 0, 1)">
 			Black color. In GDScript, this is the default value of any color.
 		</constant>
-		<constant name="BLANCHED_ALMOND" value="Color(1, 0.921569, 0.803922, 1)">
+		<constant name="BLANCHED_ALMOND" value="Color(1, 0.92156863, 0.8039216, 1)">
 			Blanched almond color.
 		</constant>
 		<constant name="BLUE" value="Color(0, 0, 1, 1)">
 			Blue color.
 		</constant>
-		<constant name="BLUE_VIOLET" value="Color(0.541176, 0.168627, 0.886275, 1)">
+		<constant name="BLUE_VIOLET" value="Color(0.5411765, 0.16862746, 0.8862745, 1)">
 			Blue violet color.
 		</constant>
-		<constant name="BROWN" value="Color(0.647059, 0.164706, 0.164706, 1)">
+		<constant name="BROWN" value="Color(0.64705884, 0.16470589, 0.16470589, 1)">
 			Brown color.
 		</constant>
-		<constant name="BURLYWOOD" value="Color(0.870588, 0.721569, 0.529412, 1)">
+		<constant name="BURLYWOOD" value="Color(0.87058824, 0.72156864, 0.5294118, 1)">
 			Burlywood color.
 		</constant>
-		<constant name="CADET_BLUE" value="Color(0.372549, 0.619608, 0.627451, 1)">
+		<constant name="CADET_BLUE" value="Color(0.37254903, 0.61960787, 0.627451, 1)">
 			Cadet blue color.
 		</constant>
-		<constant name="CHARTREUSE" value="Color(0.498039, 1, 0, 1)">
+		<constant name="CHARTREUSE" value="Color(0.49803922, 1, 0, 1)">
 			Chartreuse color.
 		</constant>
-		<constant name="CHOCOLATE" value="Color(0.823529, 0.411765, 0.117647, 1)">
+		<constant name="CHOCOLATE" value="Color(0.8235294, 0.4117647, 0.11764706, 1)">
 			Chocolate color.
 		</constant>
-		<constant name="CORAL" value="Color(1, 0.498039, 0.313726, 1)">
+		<constant name="CORAL" value="Color(1, 0.49803922, 0.3137255, 1)">
 			Coral color.
 		</constant>
-		<constant name="CORNFLOWER_BLUE" value="Color(0.392157, 0.584314, 0.929412, 1)">
+		<constant name="CORNFLOWER_BLUE" value="Color(0.39215687, 0.58431375, 0.92941177, 1)">
 			Cornflower blue color.
 		</constant>
-		<constant name="CORNSILK" value="Color(1, 0.972549, 0.862745, 1)">
+		<constant name="CORNSILK" value="Color(1, 0.972549, 0.8627451, 1)">
 			Cornsilk color.
 		</constant>
-		<constant name="CRIMSON" value="Color(0.862745, 0.0784314, 0.235294, 1)">
+		<constant name="CRIMSON" value="Color(0.8627451, 0.078431375, 0.23529412, 1)">
 			Crimson color.
 		</constant>
 		<constant name="CYAN" value="Color(0, 1, 1, 1)">
 			Cyan color.
 		</constant>
-		<constant name="DARK_BLUE" value="Color(0, 0, 0.545098, 1)">
+		<constant name="DARK_BLUE" value="Color(0, 0, 0.54509807, 1)">
 			Dark blue color.
 		</constant>
-		<constant name="DARK_CYAN" value="Color(0, 0.545098, 0.545098, 1)">
+		<constant name="DARK_CYAN" value="Color(0, 0.54509807, 0.54509807, 1)">
 			Dark cyan color.
 		</constant>
-		<constant name="DARK_GOLDENROD" value="Color(0.721569, 0.52549, 0.0431373, 1)">
+		<constant name="DARK_GOLDENROD" value="Color(0.72156864, 0.5254902, 0.043137256, 1)">
 			Dark goldenrod color.
 		</constant>
-		<constant name="DARK_GRAY" value="Color(0.662745, 0.662745, 0.662745, 1)">
+		<constant name="DARK_GRAY" value="Color(0.6627451, 0.6627451, 0.6627451, 1)">
 			Dark gray color.
 		</constant>
-		<constant name="DARK_GREEN" value="Color(0, 0.392157, 0, 1)">
+		<constant name="DARK_GREEN" value="Color(0, 0.39215687, 0, 1)">
 			Dark green color.
 		</constant>
-		<constant name="DARK_KHAKI" value="Color(0.741176, 0.717647, 0.419608, 1)">
+		<constant name="DARK_KHAKI" value="Color(0.7411765, 0.7176471, 0.41960785, 1)">
 			Dark khaki color.
 		</constant>
-		<constant name="DARK_MAGENTA" value="Color(0.545098, 0, 0.545098, 1)">
+		<constant name="DARK_MAGENTA" value="Color(0.54509807, 0, 0.54509807, 1)">
 			Dark magenta color.
 		</constant>
-		<constant name="DARK_OLIVE_GREEN" value="Color(0.333333, 0.419608, 0.184314, 1)">
+		<constant name="DARK_OLIVE_GREEN" value="Color(0.33333334, 0.41960785, 0.18431373, 1)">
 			Dark olive green color.
 		</constant>
-		<constant name="DARK_ORANGE" value="Color(1, 0.54902, 0, 1)">
+		<constant name="DARK_ORANGE" value="Color(1, 0.54901963, 0, 1)">
 			Dark orange color.
 		</constant>
-		<constant name="DARK_ORCHID" value="Color(0.6, 0.196078, 0.8, 1)">
+		<constant name="DARK_ORCHID" value="Color(0.6, 0.19607843, 0.8, 1)">
 			Dark orchid color.
 		</constant>
-		<constant name="DARK_RED" value="Color(0.545098, 0, 0, 1)">
+		<constant name="DARK_RED" value="Color(0.54509807, 0, 0, 1)">
 			Dark red color.
 		</constant>
-		<constant name="DARK_SALMON" value="Color(0.913725, 0.588235, 0.478431, 1)">
+		<constant name="DARK_SALMON" value="Color(0.9137255, 0.5882353, 0.47843137, 1)">
 			Dark salmon color.
 		</constant>
-		<constant name="DARK_SEA_GREEN" value="Color(0.560784, 0.737255, 0.560784, 1)">
+		<constant name="DARK_SEA_GREEN" value="Color(0.56078434, 0.7372549, 0.56078434, 1)">
 			Dark sea green color.
 		</constant>
-		<constant name="DARK_SLATE_BLUE" value="Color(0.282353, 0.239216, 0.545098, 1)">
+		<constant name="DARK_SLATE_BLUE" value="Color(0.28235295, 0.23921569, 0.54509807, 1)">
 			Dark slate blue color.
 		</constant>
-		<constant name="DARK_SLATE_GRAY" value="Color(0.184314, 0.309804, 0.309804, 1)">
+		<constant name="DARK_SLATE_GRAY" value="Color(0.18431373, 0.30980393, 0.30980393, 1)">
 			Dark slate gray color.
 		</constant>
-		<constant name="DARK_TURQUOISE" value="Color(0, 0.807843, 0.819608, 1)">
+		<constant name="DARK_TURQUOISE" value="Color(0, 0.80784315, 0.81960785, 1)">
 			Dark turquoise color.
 		</constant>
-		<constant name="DARK_VIOLET" value="Color(0.580392, 0, 0.827451, 1)">
+		<constant name="DARK_VIOLET" value="Color(0.5803922, 0, 0.827451, 1)">
 			Dark violet color.
 		</constant>
-		<constant name="DEEP_PINK" value="Color(1, 0.0784314, 0.576471, 1)">
+		<constant name="DEEP_PINK" value="Color(1, 0.078431375, 0.5764706, 1)">
 			Deep pink color.
 		</constant>
-		<constant name="DEEP_SKY_BLUE" value="Color(0, 0.74902, 1, 1)">
+		<constant name="DEEP_SKY_BLUE" value="Color(0, 0.7490196, 1, 1)">
 			Deep sky blue color.
 		</constant>
-		<constant name="DIM_GRAY" value="Color(0.411765, 0.411765, 0.411765, 1)">
+		<constant name="DIM_GRAY" value="Color(0.4117647, 0.4117647, 0.4117647, 1)">
 			Dim gray color.
 		</constant>
-		<constant name="DODGER_BLUE" value="Color(0.117647, 0.564706, 1, 1)">
+		<constant name="DODGER_BLUE" value="Color(0.11764706, 0.5647059, 1, 1)">
 			Dodger blue color.
 		</constant>
-		<constant name="FIREBRICK" value="Color(0.698039, 0.133333, 0.133333, 1)">
+		<constant name="FIREBRICK" value="Color(0.69803923, 0.13333334, 0.13333334, 1)">
 			Firebrick color.
 		</constant>
-		<constant name="FLORAL_WHITE" value="Color(1, 0.980392, 0.941176, 1)">
+		<constant name="FLORAL_WHITE" value="Color(1, 0.98039216, 0.9411765, 1)">
 			Floral white color.
 		</constant>
-		<constant name="FOREST_GREEN" value="Color(0.133333, 0.545098, 0.133333, 1)">
+		<constant name="FOREST_GREEN" value="Color(0.13333334, 0.54509807, 0.13333334, 1)">
 			Forest green color.
 		</constant>
 		<constant name="FUCHSIA" value="Color(1, 0, 1, 1)">
 			Fuchsia color.
 		</constant>
-		<constant name="GAINSBORO" value="Color(0.862745, 0.862745, 0.862745, 1)">
+		<constant name="GAINSBORO" value="Color(0.8627451, 0.8627451, 0.8627451, 1)">
 			Gainsboro color.
 		</constant>
 		<constant name="GHOST_WHITE" value="Color(0.972549, 0.972549, 1, 1)">
 			Ghost white color.
 		</constant>
-		<constant name="GOLD" value="Color(1, 0.843137, 0, 1)">
+		<constant name="GOLD" value="Color(1, 0.84313726, 0, 1)">
 			Gold color.
 		</constant>
-		<constant name="GOLDENROD" value="Color(0.854902, 0.647059, 0.12549, 1)">
+		<constant name="GOLDENROD" value="Color(0.85490197, 0.64705884, 0.1254902, 1)">
 			Goldenrod color.
 		</constant>
-		<constant name="GRAY" value="Color(0.745098, 0.745098, 0.745098, 1)">
+		<constant name="GRAY" value="Color(0.74509805, 0.74509805, 0.74509805, 1)">
 			Gray color.
 		</constant>
 		<constant name="GREEN" value="Color(0, 1, 0, 1)">
 			Green color.
 		</constant>
-		<constant name="GREEN_YELLOW" value="Color(0.678431, 1, 0.184314, 1)">
+		<constant name="GREEN_YELLOW" value="Color(0.6784314, 1, 0.18431373, 1)">
 			Green yellow color.
 		</constant>
-		<constant name="HONEYDEW" value="Color(0.941176, 1, 0.941176, 1)">
+		<constant name="HONEYDEW" value="Color(0.9411765, 1, 0.9411765, 1)">
 			Honeydew color.
 		</constant>
-		<constant name="HOT_PINK" value="Color(1, 0.411765, 0.705882, 1)">
+		<constant name="HOT_PINK" value="Color(1, 0.4117647, 0.7058824, 1)">
 			Hot pink color.
 		</constant>
-		<constant name="INDIAN_RED" value="Color(0.803922, 0.360784, 0.360784, 1)">
+		<constant name="INDIAN_RED" value="Color(0.8039216, 0.36078432, 0.36078432, 1)">
 			Indian red color.
 		</constant>
-		<constant name="INDIGO" value="Color(0.294118, 0, 0.509804, 1)">
+		<constant name="INDIGO" value="Color(0.29411766, 0, 0.50980395, 1)">
 			Indigo color.
 		</constant>
-		<constant name="IVORY" value="Color(1, 1, 0.941176, 1)">
+		<constant name="IVORY" value="Color(1, 1, 0.9411765, 1)">
 			Ivory color.
 		</constant>
-		<constant name="KHAKI" value="Color(0.941176, 0.901961, 0.54902, 1)">
+		<constant name="KHAKI" value="Color(0.9411765, 0.9019608, 0.54901963, 1)">
 			Khaki color.
 		</constant>
-		<constant name="LAVENDER" value="Color(0.901961, 0.901961, 0.980392, 1)">
+		<constant name="LAVENDER" value="Color(0.9019608, 0.9019608, 0.98039216, 1)">
 			Lavender color.
 		</constant>
-		<constant name="LAVENDER_BLUSH" value="Color(1, 0.941176, 0.960784, 1)">
+		<constant name="LAVENDER_BLUSH" value="Color(1, 0.9411765, 0.9607843, 1)">
 			Lavender blush color.
 		</constant>
-		<constant name="LAWN_GREEN" value="Color(0.486275, 0.988235, 0, 1)">
+		<constant name="LAWN_GREEN" value="Color(0.4862745, 0.9882353, 0, 1)">
 			Lawn green color.
 		</constant>
-		<constant name="LEMON_CHIFFON" value="Color(1, 0.980392, 0.803922, 1)">
+		<constant name="LEMON_CHIFFON" value="Color(1, 0.98039216, 0.8039216, 1)">
 			Lemon chiffon color.
 		</constant>
-		<constant name="LIGHT_BLUE" value="Color(0.678431, 0.847059, 0.901961, 1)">
+		<constant name="LIGHT_BLUE" value="Color(0.6784314, 0.84705883, 0.9019608, 1)">
 			Light blue color.
 		</constant>
-		<constant name="LIGHT_CORAL" value="Color(0.941176, 0.501961, 0.501961, 1)">
+		<constant name="LIGHT_CORAL" value="Color(0.9411765, 0.5019608, 0.5019608, 1)">
 			Light coral color.
 		</constant>
-		<constant name="LIGHT_CYAN" value="Color(0.878431, 1, 1, 1)">
+		<constant name="LIGHT_CYAN" value="Color(0.8784314, 1, 1, 1)">
 			Light cyan color.
 		</constant>
-		<constant name="LIGHT_GOLDENROD" value="Color(0.980392, 0.980392, 0.823529, 1)">
+		<constant name="LIGHT_GOLDENROD" value="Color(0.98039216, 0.98039216, 0.8235294, 1)">
 			Light goldenrod color.
 		</constant>
 		<constant name="LIGHT_GRAY" value="Color(0.827451, 0.827451, 0.827451, 1)">
 			Light gray color.
 		</constant>
-		<constant name="LIGHT_GREEN" value="Color(0.564706, 0.933333, 0.564706, 1)">
+		<constant name="LIGHT_GREEN" value="Color(0.5647059, 0.93333334, 0.5647059, 1)">
 			Light green color.
 		</constant>
-		<constant name="LIGHT_PINK" value="Color(1, 0.713726, 0.756863, 1)">
+		<constant name="LIGHT_PINK" value="Color(1, 0.7137255, 0.75686276, 1)">
 			Light pink color.
 		</constant>
-		<constant name="LIGHT_SALMON" value="Color(1, 0.627451, 0.478431, 1)">
+		<constant name="LIGHT_SALMON" value="Color(1, 0.627451, 0.47843137, 1)">
 			Light salmon color.
 		</constant>
-		<constant name="LIGHT_SEA_GREEN" value="Color(0.12549, 0.698039, 0.666667, 1)">
+		<constant name="LIGHT_SEA_GREEN" value="Color(0.1254902, 0.69803923, 0.6666667, 1)">
 			Light sea green color.
 		</constant>
-		<constant name="LIGHT_SKY_BLUE" value="Color(0.529412, 0.807843, 0.980392, 1)">
+		<constant name="LIGHT_SKY_BLUE" value="Color(0.5294118, 0.80784315, 0.98039216, 1)">
 			Light sky blue color.
 		</constant>
-		<constant name="LIGHT_SLATE_GRAY" value="Color(0.466667, 0.533333, 0.6, 1)">
+		<constant name="LIGHT_SLATE_GRAY" value="Color(0.46666667, 0.53333336, 0.6, 1)">
 			Light slate gray color.
 		</constant>
-		<constant name="LIGHT_STEEL_BLUE" value="Color(0.690196, 0.768627, 0.870588, 1)">
+		<constant name="LIGHT_STEEL_BLUE" value="Color(0.6901961, 0.76862746, 0.87058824, 1)">
 			Light steel blue color.
 		</constant>
-		<constant name="LIGHT_YELLOW" value="Color(1, 1, 0.878431, 1)">
+		<constant name="LIGHT_YELLOW" value="Color(1, 1, 0.8784314, 1)">
 			Light yellow color.
 		</constant>
 		<constant name="LIME" value="Color(0, 1, 0, 1)">
 			Lime color.
 		</constant>
-		<constant name="LIME_GREEN" value="Color(0.196078, 0.803922, 0.196078, 1)">
+		<constant name="LIME_GREEN" value="Color(0.19607843, 0.8039216, 0.19607843, 1)">
 			Lime green color.
 		</constant>
-		<constant name="LINEN" value="Color(0.980392, 0.941176, 0.901961, 1)">
+		<constant name="LINEN" value="Color(0.98039216, 0.9411765, 0.9019608, 1)">
 			Linen color.
 		</constant>
 		<constant name="MAGENTA" value="Color(1, 0, 1, 1)">
 			Magenta color.
 		</constant>
-		<constant name="MAROON" value="Color(0.690196, 0.188235, 0.376471, 1)">
+		<constant name="MAROON" value="Color(0.6901961, 0.1882353, 0.3764706, 1)">
 			Maroon color.
 		</constant>
-		<constant name="MEDIUM_AQUAMARINE" value="Color(0.4, 0.803922, 0.666667, 1)">
+		<constant name="MEDIUM_AQUAMARINE" value="Color(0.4, 0.8039216, 0.6666667, 1)">
 			Medium aquamarine color.
 		</constant>
-		<constant name="MEDIUM_BLUE" value="Color(0, 0, 0.803922, 1)">
+		<constant name="MEDIUM_BLUE" value="Color(0, 0, 0.8039216, 1)">
 			Medium blue color.
 		</constant>
-		<constant name="MEDIUM_ORCHID" value="Color(0.729412, 0.333333, 0.827451, 1)">
+		<constant name="MEDIUM_ORCHID" value="Color(0.7294118, 0.33333334, 0.827451, 1)">
 			Medium orchid color.
 		</constant>
-		<constant name="MEDIUM_PURPLE" value="Color(0.576471, 0.439216, 0.858824, 1)">
+		<constant name="MEDIUM_PURPLE" value="Color(0.5764706, 0.4392157, 0.85882354, 1)">
 			Medium purple color.
 		</constant>
-		<constant name="MEDIUM_SEA_GREEN" value="Color(0.235294, 0.701961, 0.443137, 1)">
+		<constant name="MEDIUM_SEA_GREEN" value="Color(0.23529412, 0.7019608, 0.44313726, 1)">
 			Medium sea green color.
 		</constant>
-		<constant name="MEDIUM_SLATE_BLUE" value="Color(0.482353, 0.407843, 0.933333, 1)">
+		<constant name="MEDIUM_SLATE_BLUE" value="Color(0.48235294, 0.40784314, 0.93333334, 1)">
 			Medium slate blue color.
 		</constant>
-		<constant name="MEDIUM_SPRING_GREEN" value="Color(0, 0.980392, 0.603922, 1)">
+		<constant name="MEDIUM_SPRING_GREEN" value="Color(0, 0.98039216, 0.6039216, 1)">
 			Medium spring green color.
 		</constant>
-		<constant name="MEDIUM_TURQUOISE" value="Color(0.282353, 0.819608, 0.8, 1)">
+		<constant name="MEDIUM_TURQUOISE" value="Color(0.28235295, 0.81960785, 0.8, 1)">
 			Medium turquoise color.
 		</constant>
-		<constant name="MEDIUM_VIOLET_RED" value="Color(0.780392, 0.0823529, 0.521569, 1)">
+		<constant name="MEDIUM_VIOLET_RED" value="Color(0.78039217, 0.08235294, 0.52156866, 1)">
 			Medium violet red color.
 		</constant>
-		<constant name="MIDNIGHT_BLUE" value="Color(0.0980392, 0.0980392, 0.439216, 1)">
+		<constant name="MIDNIGHT_BLUE" value="Color(0.09803922, 0.09803922, 0.4392157, 1)">
 			Midnight blue color.
 		</constant>
-		<constant name="MINT_CREAM" value="Color(0.960784, 1, 0.980392, 1)">
+		<constant name="MINT_CREAM" value="Color(0.9607843, 1, 0.98039216, 1)">
 			Mint cream color.
 		</constant>
-		<constant name="MISTY_ROSE" value="Color(1, 0.894118, 0.882353, 1)">
+		<constant name="MISTY_ROSE" value="Color(1, 0.89411765, 0.88235295, 1)">
 			Misty rose color.
 		</constant>
-		<constant name="MOCCASIN" value="Color(1, 0.894118, 0.709804, 1)">
+		<constant name="MOCCASIN" value="Color(1, 0.89411765, 0.70980394, 1)">
 			Moccasin color.
 		</constant>
-		<constant name="NAVAJO_WHITE" value="Color(1, 0.870588, 0.678431, 1)">
+		<constant name="NAVAJO_WHITE" value="Color(1, 0.87058824, 0.6784314, 1)">
 			Navajo white color.
 		</constant>
-		<constant name="NAVY_BLUE" value="Color(0, 0, 0.501961, 1)">
+		<constant name="NAVY_BLUE" value="Color(0, 0, 0.5019608, 1)">
 			Navy blue color.
 		</constant>
-		<constant name="OLD_LACE" value="Color(0.992157, 0.960784, 0.901961, 1)">
+		<constant name="OLD_LACE" value="Color(0.99215686, 0.9607843, 0.9019608, 1)">
 			Old lace color.
 		</constant>
-		<constant name="OLIVE" value="Color(0.501961, 0.501961, 0, 1)">
+		<constant name="OLIVE" value="Color(0.5019608, 0.5019608, 0, 1)">
 			Olive color.
 		</constant>
-		<constant name="OLIVE_DRAB" value="Color(0.419608, 0.556863, 0.137255, 1)">
+		<constant name="OLIVE_DRAB" value="Color(0.41960785, 0.5568628, 0.13725491, 1)">
 			Olive drab color.
 		</constant>
-		<constant name="ORANGE" value="Color(1, 0.647059, 0, 1)">
+		<constant name="ORANGE" value="Color(1, 0.64705884, 0, 1)">
 			Orange color.
 		</constant>
-		<constant name="ORANGE_RED" value="Color(1, 0.270588, 0, 1)">
+		<constant name="ORANGE_RED" value="Color(1, 0.27058825, 0, 1)">
 			Orange red color.
 		</constant>
-		<constant name="ORCHID" value="Color(0.854902, 0.439216, 0.839216, 1)">
+		<constant name="ORCHID" value="Color(0.85490197, 0.4392157, 0.8392157, 1)">
 			Orchid color.
 		</constant>
-		<constant name="PALE_GOLDENROD" value="Color(0.933333, 0.909804, 0.666667, 1)">
+		<constant name="PALE_GOLDENROD" value="Color(0.93333334, 0.9098039, 0.6666667, 1)">
 			Pale goldenrod color.
 		</constant>
-		<constant name="PALE_GREEN" value="Color(0.596078, 0.984314, 0.596078, 1)">
+		<constant name="PALE_GREEN" value="Color(0.59607846, 0.9843137, 0.59607846, 1)">
 			Pale green color.
 		</constant>
-		<constant name="PALE_TURQUOISE" value="Color(0.686275, 0.933333, 0.933333, 1)">
+		<constant name="PALE_TURQUOISE" value="Color(0.6862745, 0.93333334, 0.93333334, 1)">
 			Pale turquoise color.
 		</constant>
-		<constant name="PALE_VIOLET_RED" value="Color(0.858824, 0.439216, 0.576471, 1)">
+		<constant name="PALE_VIOLET_RED" value="Color(0.85882354, 0.4392157, 0.5764706, 1)">
 			Pale violet red color.
 		</constant>
-		<constant name="PAPAYA_WHIP" value="Color(1, 0.937255, 0.835294, 1)">
+		<constant name="PAPAYA_WHIP" value="Color(1, 0.9372549, 0.8352941, 1)">
 			Papaya whip color.
 		</constant>
-		<constant name="PEACH_PUFF" value="Color(1, 0.854902, 0.72549, 1)">
+		<constant name="PEACH_PUFF" value="Color(1, 0.85490197, 0.7254902, 1)">
 			Peach puff color.
 		</constant>
-		<constant name="PERU" value="Color(0.803922, 0.521569, 0.247059, 1)">
+		<constant name="PERU" value="Color(0.8039216, 0.52156866, 0.24705882, 1)">
 			Peru color.
 		</constant>
-		<constant name="PINK" value="Color(1, 0.752941, 0.796078, 1)">
+		<constant name="PINK" value="Color(1, 0.7529412, 0.79607844, 1)">
 			Pink color.
 		</constant>
-		<constant name="PLUM" value="Color(0.866667, 0.627451, 0.866667, 1)">
+		<constant name="PLUM" value="Color(0.8666667, 0.627451, 0.8666667, 1)">
 			Plum color.
 		</constant>
-		<constant name="POWDER_BLUE" value="Color(0.690196, 0.878431, 0.901961, 1)">
+		<constant name="POWDER_BLUE" value="Color(0.6901961, 0.8784314, 0.9019608, 1)">
 			Powder blue color.
 		</constant>
-		<constant name="PURPLE" value="Color(0.627451, 0.12549, 0.941176, 1)">
+		<constant name="PURPLE" value="Color(0.627451, 0.1254902, 0.9411765, 1)">
 			Purple color.
 		</constant>
 		<constant name="REBECCA_PURPLE" value="Color(0.4, 0.2, 0.6, 1)">
@@ -883,97 +883,97 @@
 		<constant name="RED" value="Color(1, 0, 0, 1)">
 			Red color.
 		</constant>
-		<constant name="ROSY_BROWN" value="Color(0.737255, 0.560784, 0.560784, 1)">
+		<constant name="ROSY_BROWN" value="Color(0.7372549, 0.56078434, 0.56078434, 1)">
 			Rosy brown color.
 		</constant>
-		<constant name="ROYAL_BLUE" value="Color(0.254902, 0.411765, 0.882353, 1)">
+		<constant name="ROYAL_BLUE" value="Color(0.25490198, 0.4117647, 0.88235295, 1)">
 			Royal blue color.
 		</constant>
-		<constant name="SADDLE_BROWN" value="Color(0.545098, 0.270588, 0.0745098, 1)">
+		<constant name="SADDLE_BROWN" value="Color(0.54509807, 0.27058825, 0.07450981, 1)">
 			Saddle brown color.
 		</constant>
-		<constant name="SALMON" value="Color(0.980392, 0.501961, 0.447059, 1)">
+		<constant name="SALMON" value="Color(0.98039216, 0.5019608, 0.44705883, 1)">
 			Salmon color.
 		</constant>
-		<constant name="SANDY_BROWN" value="Color(0.956863, 0.643137, 0.376471, 1)">
+		<constant name="SANDY_BROWN" value="Color(0.95686275, 0.6431373, 0.3764706, 1)">
 			Sandy brown color.
 		</constant>
-		<constant name="SEA_GREEN" value="Color(0.180392, 0.545098, 0.341176, 1)">
+		<constant name="SEA_GREEN" value="Color(0.18039216, 0.54509807, 0.34117648, 1)">
 			Sea green color.
 		</constant>
-		<constant name="SEASHELL" value="Color(1, 0.960784, 0.933333, 1)">
+		<constant name="SEASHELL" value="Color(1, 0.9607843, 0.93333334, 1)">
 			Seashell color.
 		</constant>
-		<constant name="SIENNA" value="Color(0.627451, 0.321569, 0.176471, 1)">
+		<constant name="SIENNA" value="Color(0.627451, 0.32156864, 0.1764706, 1)">
 			Sienna color.
 		</constant>
-		<constant name="SILVER" value="Color(0.752941, 0.752941, 0.752941, 1)">
+		<constant name="SILVER" value="Color(0.7529412, 0.7529412, 0.7529412, 1)">
 			Silver color.
 		</constant>
-		<constant name="SKY_BLUE" value="Color(0.529412, 0.807843, 0.921569, 1)">
+		<constant name="SKY_BLUE" value="Color(0.5294118, 0.80784315, 0.92156863, 1)">
 			Sky blue color.
 		</constant>
-		<constant name="SLATE_BLUE" value="Color(0.415686, 0.352941, 0.803922, 1)">
+		<constant name="SLATE_BLUE" value="Color(0.41568628, 0.3529412, 0.8039216, 1)">
 			Slate blue color.
 		</constant>
-		<constant name="SLATE_GRAY" value="Color(0.439216, 0.501961, 0.564706, 1)">
+		<constant name="SLATE_GRAY" value="Color(0.4392157, 0.5019608, 0.5647059, 1)">
 			Slate gray color.
 		</constant>
-		<constant name="SNOW" value="Color(1, 0.980392, 0.980392, 1)">
+		<constant name="SNOW" value="Color(1, 0.98039216, 0.98039216, 1)">
 			Snow color.
 		</constant>
-		<constant name="SPRING_GREEN" value="Color(0, 1, 0.498039, 1)">
+		<constant name="SPRING_GREEN" value="Color(0, 1, 0.49803922, 1)">
 			Spring green color.
 		</constant>
-		<constant name="STEEL_BLUE" value="Color(0.27451, 0.509804, 0.705882, 1)">
+		<constant name="STEEL_BLUE" value="Color(0.27450982, 0.50980395, 0.7058824, 1)">
 			Steel blue color.
 		</constant>
-		<constant name="TAN" value="Color(0.823529, 0.705882, 0.54902, 1)">
+		<constant name="TAN" value="Color(0.8235294, 0.7058824, 0.54901963, 1)">
 			Tan color.
 		</constant>
-		<constant name="TEAL" value="Color(0, 0.501961, 0.501961, 1)">
+		<constant name="TEAL" value="Color(0, 0.5019608, 0.5019608, 1)">
 			Teal color.
 		</constant>
-		<constant name="THISTLE" value="Color(0.847059, 0.74902, 0.847059, 1)">
+		<constant name="THISTLE" value="Color(0.84705883, 0.7490196, 0.84705883, 1)">
 			Thistle color.
 		</constant>
-		<constant name="TOMATO" value="Color(1, 0.388235, 0.278431, 1)">
+		<constant name="TOMATO" value="Color(1, 0.3882353, 0.2784314, 1)">
 			Tomato color.
 		</constant>
 		<constant name="TRANSPARENT" value="Color(1, 1, 1, 0)">
 			Transparent color (white with zero alpha).
 		</constant>
-		<constant name="TURQUOISE" value="Color(0.25098, 0.878431, 0.815686, 1)">
+		<constant name="TURQUOISE" value="Color(0.2509804, 0.8784314, 0.8156863, 1)">
 			Turquoise color.
 		</constant>
-		<constant name="VIOLET" value="Color(0.933333, 0.509804, 0.933333, 1)">
+		<constant name="VIOLET" value="Color(0.93333334, 0.50980395, 0.93333334, 1)">
 			Violet color.
 		</constant>
-		<constant name="WEB_GRAY" value="Color(0.501961, 0.501961, 0.501961, 1)">
+		<constant name="WEB_GRAY" value="Color(0.5019608, 0.5019608, 0.5019608, 1)">
 			Web gray color.
 		</constant>
-		<constant name="WEB_GREEN" value="Color(0, 0.501961, 0, 1)">
+		<constant name="WEB_GREEN" value="Color(0, 0.5019608, 0, 1)">
 			Web green color.
 		</constant>
-		<constant name="WEB_MAROON" value="Color(0.501961, 0, 0, 1)">
+		<constant name="WEB_MAROON" value="Color(0.5019608, 0, 0, 1)">
 			Web maroon color.
 		</constant>
-		<constant name="WEB_PURPLE" value="Color(0.501961, 0, 0.501961, 1)">
+		<constant name="WEB_PURPLE" value="Color(0.5019608, 0, 0.5019608, 1)">
 			Web purple color.
 		</constant>
-		<constant name="WHEAT" value="Color(0.960784, 0.870588, 0.701961, 1)">
+		<constant name="WHEAT" value="Color(0.9607843, 0.87058824, 0.7019608, 1)">
 			Wheat color.
 		</constant>
 		<constant name="WHITE" value="Color(1, 1, 1, 1)">
 			White color.
 		</constant>
-		<constant name="WHITE_SMOKE" value="Color(0.960784, 0.960784, 0.960784, 1)">
+		<constant name="WHITE_SMOKE" value="Color(0.9607843, 0.9607843, 0.9607843, 1)">
 			White smoke color.
 		</constant>
 		<constant name="YELLOW" value="Color(1, 1, 0, 1)">
 			Yellow color.
 		</constant>
-		<constant name="YELLOW_GREEN" value="Color(0.603922, 0.803922, 0.196078, 1)">
+		<constant name="YELLOW_GREEN" value="Color(0.6039216, 0.8039216, 0.19607843, 1)">
 			Yellow green color.
 		</constant>
 	</constants>

+ 2 - 2
doc/classes/ConeTwistJoint3D.xml

@@ -36,13 +36,13 @@
 		<member name="softness" type="float" setter="set_param" getter="get_param" default="0.8">
 			The ease with which the joint starts to twist. If it's too low, it takes more force to start twisting the joint.
 		</member>
-		<member name="swing_span" type="float" setter="set_param" getter="get_param" default="0.785398">
+		<member name="swing_span" type="float" setter="set_param" getter="get_param" default="0.7853982">
 			Swing is rotation from side to side, around the axis perpendicular to the twist axis.
 			The swing span defines, how much rotation will not get corrected along the swing axis.
 			Could be defined as looseness in the [ConeTwistJoint3D].
 			If below 0.05, this behavior is locked.
 		</member>
-		<member name="twist_span" type="float" setter="set_param" getter="get_param" default="3.14159">
+		<member name="twist_span" type="float" setter="set_param" getter="get_param" default="3.1415927">
 			Twist is the rotation around the twist axis, this value defined how far the joint can twist.
 			Twist is locked if below 0.05.
 		</member>

+ 2 - 2
doc/classes/GraphEdit.xml

@@ -397,10 +397,10 @@
 		<member name="zoom" type="float" setter="set_zoom" getter="get_zoom" default="1.0">
 			The current zoom value.
 		</member>
-		<member name="zoom_max" type="float" setter="set_zoom_max" getter="get_zoom_max" default="2.0736">
+		<member name="zoom_max" type="float" setter="set_zoom_max" getter="get_zoom_max" default="2.0736003">
 			The upper zoom limit.
 		</member>
-		<member name="zoom_min" type="float" setter="set_zoom_min" getter="get_zoom_min" default="0.232568">
+		<member name="zoom_min" type="float" setter="set_zoom_min" getter="get_zoom_min" default="0.23256795">
 			The lower zoom limit.
 		</member>
 		<member name="zoom_step" type="float" setter="set_zoom_step" getter="get_zoom_step" default="1.2">

+ 2 - 2
doc/classes/HingeJoint3D.xml

@@ -47,7 +47,7 @@
 		<member name="angular_limit/enable" type="bool" setter="set_flag" getter="get_flag" default="false">
 			If [code]true[/code], the hinges maximum and minimum rotation, defined by [member angular_limit/lower] and [member angular_limit/upper] has effects.
 		</member>
-		<member name="angular_limit/lower" type="float" setter="set_param" getter="get_param" default="-1.5708">
+		<member name="angular_limit/lower" type="float" setter="set_param" getter="get_param" default="-1.5707964">
 			The minimum rotation. Only active if [member angular_limit/enable] is [code]true[/code].
 		</member>
 		<member name="angular_limit/relaxation" type="float" setter="set_param" getter="get_param" default="1.0">
@@ -55,7 +55,7 @@
 		</member>
 		<member name="angular_limit/softness" type="float" setter="set_param" getter="get_param" default="0.9" deprecated="This property is never set by the engine and is kept for compatibility purposes.">
 		</member>
-		<member name="angular_limit/upper" type="float" setter="set_param" getter="get_param" default="1.5708">
+		<member name="angular_limit/upper" type="float" setter="set_param" getter="get_param" default="1.5707964">
 			The maximum rotation. Only active if [member angular_limit/enable] is [code]true[/code].
 		</member>
 		<member name="motor/enable" type="bool" setter="set_flag" getter="get_flag" default="false">

+ 2 - 2
doc/classes/Parallax2D.xml

@@ -20,10 +20,10 @@
 		<member name="ignore_camera_scroll" type="bool" setter="set_ignore_camera_scroll" getter="is_ignore_camera_scroll" default="false">
 			If [code]true[/code], [Parallax2D]'s position is not affected by the position of the camera.
 		</member>
-		<member name="limit_begin" type="Vector2" setter="set_limit_begin" getter="get_limit_begin" default="Vector2(-1e+07, -1e+07)">
+		<member name="limit_begin" type="Vector2" setter="set_limit_begin" getter="get_limit_begin" default="Vector2(-10000000, -10000000)">
 			Top-left limits for scrolling to begin. If the camera is outside of this limit, the [Parallax2D] stops scrolling. Must be lower than [member limit_end] minus the viewport size to work.
 		</member>
-		<member name="limit_end" type="Vector2" setter="set_limit_end" getter="get_limit_end" default="Vector2(1e+07, 1e+07)">
+		<member name="limit_end" type="Vector2" setter="set_limit_end" getter="get_limit_end" default="Vector2(10000000, 10000000)">
 			Bottom-right limits for scrolling to end. If the camera is outside of this limit, the [Parallax2D] will stop scrolling. Must be higher than [member limit_begin] and the viewport size combined to work.
 		</member>
 		<member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="2" />

+ 5 - 5
doc/classes/ProjectSettings.xml

@@ -2403,7 +2403,7 @@
 		<member name="physics/2d/run_on_separate_thread" type="bool" setter="" getter="" default="false">
 			If [code]true[/code], the 2D physics server runs on a separate thread, making better use of multi-core CPUs. If [code]false[/code], the 2D physics server runs on the main thread. Running the physics server on a separate thread can increase performance, but restricts API access to only physics process.
 		</member>
-		<member name="physics/2d/sleep_threshold_angular" type="float" setter="" getter="" default="0.139626">
+		<member name="physics/2d/sleep_threshold_angular" type="float" setter="" getter="" default="0.13962634">
 			Threshold angular velocity under which a 2D physics body will be considered inactive. See [constant PhysicsServer2D.SPACE_PARAM_BODY_ANGULAR_VELOCITY_SLEEP_THRESHOLD].
 		</member>
 		<member name="physics/2d/sleep_threshold_linear" type="float" setter="" getter="" default="2.0">
@@ -2486,7 +2486,7 @@
 			If [code]true[/code], the 3D physics server runs on a separate thread, making better use of multi-core CPUs. If [code]false[/code], the 3D physics server runs on the main thread. Running the physics server on a separate thread can increase performance, but restricts API access to only physics process.
 			[b]Note:[/b] When [member physics/3d/physics_engine] is set to [code]Jolt Physics[/code], enabling this setting will prevent the 3D physics server from being able to provide any context when reporting errors and warnings, and will instead always refer to nodes as [code]&lt;unknown&gt;[/code].
 		</member>
-		<member name="physics/3d/sleep_threshold_angular" type="float" setter="" getter="" default="0.139626">
+		<member name="physics/3d/sleep_threshold_angular" type="float" setter="" getter="" default="0.13962634">
 			Threshold angular velocity under which a 3D physics body will be considered inactive. See [constant PhysicsServer3D.SPACE_PARAM_BODY_ANGULAR_VELOCITY_SLEEP_THRESHOLD].
 		</member>
 		<member name="physics/3d/sleep_threshold_linear" type="float" setter="" getter="" default="0.1">
@@ -2535,7 +2535,7 @@
 			[b]Note:[/b] This property is only read when the project starts. To change the physics FPS at runtime, set [member Engine.physics_ticks_per_second] instead.
 			[b]Note:[/b] Only [member physics/common/max_physics_steps_per_frame] physics ticks may be simulated per rendered frame at most. If more physics ticks have to be simulated per rendered frame to keep up with rendering, the project will appear to slow down (even if [code]delta[/code] is used consistently in physics calculations). Therefore, it is recommended to also increase [member physics/common/max_physics_steps_per_frame] if increasing [member physics/common/physics_ticks_per_second] significantly above its default value.
 		</member>
-		<member name="physics/jolt_physics_3d/collisions/active_edge_threshold" type="float" setter="" getter="" default="0.872665">
+		<member name="physics/jolt_physics_3d/collisions/active_edge_threshold" type="float" setter="" getter="" default="0.87266463">
 			The maximum angle, in radians, between two adjacent triangles in a [ConcavePolygonShape3D] or [HeightMapShape3D] for which the edge between those triangles is considered inactive.
 			Collisions against an inactive edge will have its normal overridden to instead be the surface normal of the triangle. This can help alleviate ghost collisions.
 			[b]Note:[/b] Setting this too high can result in objects not depenetrating properly.
@@ -2551,7 +2551,7 @@
 			Which of the two nodes bound by a joint should represent the world when one of the two is omitted, as either [member Joint3D.node_a] or [member Joint3D.node_b]. This can be thought of as having the omitted node be a [StaticBody3D] at the joint's position. Joint limits are more easily expressed when [member Joint3D.node_a] represents the world.
 			[b]Note:[/b] In Godot Physics, only [member Joint3D.node_b] can represent the world.
 		</member>
-		<member name="physics/jolt_physics_3d/limits/max_angular_velocity" type="float" setter="" getter="" default="47.1239">
+		<member name="physics/jolt_physics_3d/limits/max_angular_velocity" type="float" setter="" getter="" default="47.12389">
 			The maximum angular velocity that a [RigidBody3D] can reach, in radians per second.
 			This is mainly used as a fail-safe, to prevent the simulation from exploding, as fast-moving objects colliding with complex physics structures can otherwise cause them to go out of control. Fast-moving objects can also cause a lot of stress on the collision detection system, which can slow down the simulation considerably.
 		</member>
@@ -2611,7 +2611,7 @@
 			How much of the position error of a [RigidBody3D] to fix during a physics step, where [code]0.0[/code] is none and [code]1.0[/code] is the full amount. This affects things like how quickly bodies depenetrate.
 			[b]Note:[/b] Setting this value too high can make [RigidBody3D] nodes unstable.
 		</member>
-		<member name="physics/jolt_physics_3d/simulation/body_pair_contact_cache_angle_threshold" type="float" setter="" getter="" default="0.0349066">
+		<member name="physics/jolt_physics_3d/simulation/body_pair_contact_cache_angle_threshold" type="float" setter="" getter="" default="0.034906585">
 			The maximum relative angle by which a body pair can move and still reuse the collision results from the previous physics step, in radians.
 		</member>
 		<member name="physics/jolt_physics_3d/simulation/body_pair_contact_cache_distance_threshold" type="float" setter="" getter="" default="0.001">

+ 2 - 1
editor/doc_tools.cpp

@@ -33,6 +33,7 @@
 #include "core/config/engine.h"
 #include "core/config/project_settings.h"
 #include "core/core_constants.h"
+#include "core/doc_data.h"
 #include "core/io/compression.h"
 #include "core/io/dir_access.h"
 #include "core/io/resource_importer.h"
@@ -930,7 +931,7 @@ void DocTools::generate(BitField<GenerateFlags> p_flags) {
 			DocData::ConstantDoc constant;
 			constant.name = E;
 			Variant value = Variant::get_constant_value(Variant::Type(i), E);
-			constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string().replace_char('\n', ' ');
+			constant.value = DocData::get_default_value_string(value);
 			constant.is_value_valid = true;
 			constant.type = Variant::get_type_name(value.get_type());
 			c.constants.push_back(constant);

+ 140 - 0
misc/extension_api_validation/4.4-stable.expected

@@ -111,3 +111,143 @@ Validate extension JSON: Error: Field 'classes/EditorUndoRedoManager/methods/cre
 Validate extension JSON: Error: Field 'classes/EditorUndoRedoManager/methods/create_action/arguments': size changed value in new API, from 3 to 5.
 
 New argument added. Compatibility method registered.
+
+
+GH-98750
+--------
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/ALICE_BLUE': value changed value in new API, from "Color(0.941176, 0.972549, 1, 1)" to "Color(0.9411765, 0.972549, 1, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/ANTIQUE_WHITE': value changed value in new API, from "Color(0.980392, 0.921569, 0.843137, 1)" to "Color(0.98039216, 0.92156863, 0.84313726, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/AQUAMARINE': value changed value in new API, from "Color(0.498039, 1, 0.831373, 1)" to "Color(0.49803922, 1, 0.83137256, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/AZURE': value changed value in new API, from "Color(0.941176, 1, 1, 1)" to "Color(0.9411765, 1, 1, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/BEIGE': value changed value in new API, from "Color(0.960784, 0.960784, 0.862745, 1)" to "Color(0.9607843, 0.9607843, 0.8627451, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/BISQUE': value changed value in new API, from "Color(1, 0.894118, 0.768627, 1)" to "Color(1, 0.89411765, 0.76862746, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/BLANCHED_ALMOND': value changed value in new API, from "Color(1, 0.921569, 0.803922, 1)" to "Color(1, 0.92156863, 0.8039216, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/BLUE_VIOLET': value changed value in new API, from "Color(0.541176, 0.168627, 0.886275, 1)" to "Color(0.5411765, 0.16862746, 0.8862745, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/BROWN': value changed value in new API, from "Color(0.647059, 0.164706, 0.164706, 1)" to "Color(0.64705884, 0.16470589, 0.16470589, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/BURLYWOOD': value changed value in new API, from "Color(0.870588, 0.721569, 0.529412, 1)" to "Color(0.87058824, 0.72156864, 0.5294118, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/CADET_BLUE': value changed value in new API, from "Color(0.372549, 0.619608, 0.627451, 1)" to "Color(0.37254903, 0.61960787, 0.627451, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/CHARTREUSE': value changed value in new API, from "Color(0.498039, 1, 0, 1)" to "Color(0.49803922, 1, 0, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/CHOCOLATE': value changed value in new API, from "Color(0.823529, 0.411765, 0.117647, 1)" to "Color(0.8235294, 0.4117647, 0.11764706, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/CORAL': value changed value in new API, from "Color(1, 0.498039, 0.313726, 1)" to "Color(1, 0.49803922, 0.3137255, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/CORNFLOWER_BLUE': value changed value in new API, from "Color(0.392157, 0.584314, 0.929412, 1)" to "Color(0.39215687, 0.58431375, 0.92941177, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/CORNSILK': value changed value in new API, from "Color(1, 0.972549, 0.862745, 1)" to "Color(1, 0.972549, 0.8627451, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/CRIMSON': value changed value in new API, from "Color(0.862745, 0.0784314, 0.235294, 1)" to "Color(0.8627451, 0.078431375, 0.23529412, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_BLUE': value changed value in new API, from "Color(0, 0, 0.545098, 1)" to "Color(0, 0, 0.54509807, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_CYAN': value changed value in new API, from "Color(0, 0.545098, 0.545098, 1)" to "Color(0, 0.54509807, 0.54509807, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_GOLDENROD': value changed value in new API, from "Color(0.721569, 0.52549, 0.0431373, 1)" to "Color(0.72156864, 0.5254902, 0.043137256, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_GRAY': value changed value in new API, from "Color(0.662745, 0.662745, 0.662745, 1)" to "Color(0.6627451, 0.6627451, 0.6627451, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_GREEN': value changed value in new API, from "Color(0, 0.392157, 0, 1)" to "Color(0, 0.39215687, 0, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_KHAKI': value changed value in new API, from "Color(0.741176, 0.717647, 0.419608, 1)" to "Color(0.7411765, 0.7176471, 0.41960785, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_MAGENTA': value changed value in new API, from "Color(0.545098, 0, 0.545098, 1)" to "Color(0.54509807, 0, 0.54509807, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_OLIVE_GREEN': value changed value in new API, from "Color(0.333333, 0.419608, 0.184314, 1)" to "Color(0.33333334, 0.41960785, 0.18431373, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_ORANGE': value changed value in new API, from "Color(1, 0.54902, 0, 1)" to "Color(1, 0.54901963, 0, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_ORCHID': value changed value in new API, from "Color(0.6, 0.196078, 0.8, 1)" to "Color(0.6, 0.19607843, 0.8, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_RED': value changed value in new API, from "Color(0.545098, 0, 0, 1)" to "Color(0.54509807, 0, 0, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_SALMON': value changed value in new API, from "Color(0.913725, 0.588235, 0.478431, 1)" to "Color(0.9137255, 0.5882353, 0.47843137, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_SEA_GREEN': value changed value in new API, from "Color(0.560784, 0.737255, 0.560784, 1)" to "Color(0.56078434, 0.7372549, 0.56078434, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_SLATE_BLUE': value changed value in new API, from "Color(0.282353, 0.239216, 0.545098, 1)" to "Color(0.28235295, 0.23921569, 0.54509807, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_SLATE_GRAY': value changed value in new API, from "Color(0.184314, 0.309804, 0.309804, 1)" to "Color(0.18431373, 0.30980393, 0.30980393, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_TURQUOISE': value changed value in new API, from "Color(0, 0.807843, 0.819608, 1)" to "Color(0, 0.80784315, 0.81960785, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DARK_VIOLET': value changed value in new API, from "Color(0.580392, 0, 0.827451, 1)" to "Color(0.5803922, 0, 0.827451, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DEEP_PINK': value changed value in new API, from "Color(1, 0.0784314, 0.576471, 1)" to "Color(1, 0.078431375, 0.5764706, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DEEP_SKY_BLUE': value changed value in new API, from "Color(0, 0.74902, 1, 1)" to "Color(0, 0.7490196, 1, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DIM_GRAY': value changed value in new API, from "Color(0.411765, 0.411765, 0.411765, 1)" to "Color(0.4117647, 0.4117647, 0.4117647, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/DODGER_BLUE': value changed value in new API, from "Color(0.117647, 0.564706, 1, 1)" to "Color(0.11764706, 0.5647059, 1, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/FIREBRICK': value changed value in new API, from "Color(0.698039, 0.133333, 0.133333, 1)" to "Color(0.69803923, 0.13333334, 0.13333334, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/FLORAL_WHITE': value changed value in new API, from "Color(1, 0.980392, 0.941176, 1)" to "Color(1, 0.98039216, 0.9411765, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/FOREST_GREEN': value changed value in new API, from "Color(0.133333, 0.545098, 0.133333, 1)" to "Color(0.13333334, 0.54509807, 0.13333334, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/GAINSBORO': value changed value in new API, from "Color(0.862745, 0.862745, 0.862745, 1)" to "Color(0.8627451, 0.8627451, 0.8627451, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/GOLD': value changed value in new API, from "Color(1, 0.843137, 0, 1)" to "Color(1, 0.84313726, 0, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/GOLDENROD': value changed value in new API, from "Color(0.854902, 0.647059, 0.12549, 1)" to "Color(0.85490197, 0.64705884, 0.1254902, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/GRAY': value changed value in new API, from "Color(0.745098, 0.745098, 0.745098, 1)" to "Color(0.74509805, 0.74509805, 0.74509805, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/GREEN_YELLOW': value changed value in new API, from "Color(0.678431, 1, 0.184314, 1)" to "Color(0.6784314, 1, 0.18431373, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/HONEYDEW': value changed value in new API, from "Color(0.941176, 1, 0.941176, 1)" to "Color(0.9411765, 1, 0.9411765, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/HOT_PINK': value changed value in new API, from "Color(1, 0.411765, 0.705882, 1)" to "Color(1, 0.4117647, 0.7058824, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/INDIAN_RED': value changed value in new API, from "Color(0.803922, 0.360784, 0.360784, 1)" to "Color(0.8039216, 0.36078432, 0.36078432, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/INDIGO': value changed value in new API, from "Color(0.294118, 0, 0.509804, 1)" to "Color(0.29411766, 0, 0.50980395, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/IVORY': value changed value in new API, from "Color(1, 1, 0.941176, 1)" to "Color(1, 1, 0.9411765, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/KHAKI': value changed value in new API, from "Color(0.941176, 0.901961, 0.54902, 1)" to "Color(0.9411765, 0.9019608, 0.54901963, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LAVENDER': value changed value in new API, from "Color(0.901961, 0.901961, 0.980392, 1)" to "Color(0.9019608, 0.9019608, 0.98039216, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LAVENDER_BLUSH': value changed value in new API, from "Color(1, 0.941176, 0.960784, 1)" to "Color(1, 0.9411765, 0.9607843, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LAWN_GREEN': value changed value in new API, from "Color(0.486275, 0.988235, 0, 1)" to "Color(0.4862745, 0.9882353, 0, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LEMON_CHIFFON': value changed value in new API, from "Color(1, 0.980392, 0.803922, 1)" to "Color(1, 0.98039216, 0.8039216, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LIGHT_BLUE': value changed value in new API, from "Color(0.678431, 0.847059, 0.901961, 1)" to "Color(0.6784314, 0.84705883, 0.9019608, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LIGHT_CORAL': value changed value in new API, from "Color(0.941176, 0.501961, 0.501961, 1)" to "Color(0.9411765, 0.5019608, 0.5019608, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LIGHT_CYAN': value changed value in new API, from "Color(0.878431, 1, 1, 1)" to "Color(0.8784314, 1, 1, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LIGHT_GOLDENROD': value changed value in new API, from "Color(0.980392, 0.980392, 0.823529, 1)" to "Color(0.98039216, 0.98039216, 0.8235294, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LIGHT_GREEN': value changed value in new API, from "Color(0.564706, 0.933333, 0.564706, 1)" to "Color(0.5647059, 0.93333334, 0.5647059, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LIGHT_PINK': value changed value in new API, from "Color(1, 0.713726, 0.756863, 1)" to "Color(1, 0.7137255, 0.75686276, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LIGHT_SALMON': value changed value in new API, from "Color(1, 0.627451, 0.478431, 1)" to "Color(1, 0.627451, 0.47843137, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LIGHT_SEA_GREEN': value changed value in new API, from "Color(0.12549, 0.698039, 0.666667, 1)" to "Color(0.1254902, 0.69803923, 0.6666667, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LIGHT_SKY_BLUE': value changed value in new API, from "Color(0.529412, 0.807843, 0.980392, 1)" to "Color(0.5294118, 0.80784315, 0.98039216, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LIGHT_SLATE_GRAY': value changed value in new API, from "Color(0.466667, 0.533333, 0.6, 1)" to "Color(0.46666667, 0.53333336, 0.6, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LIGHT_STEEL_BLUE': value changed value in new API, from "Color(0.690196, 0.768627, 0.870588, 1)" to "Color(0.6901961, 0.76862746, 0.87058824, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LIGHT_YELLOW': value changed value in new API, from "Color(1, 1, 0.878431, 1)" to "Color(1, 1, 0.8784314, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LIME_GREEN': value changed value in new API, from "Color(0.196078, 0.803922, 0.196078, 1)" to "Color(0.19607843, 0.8039216, 0.19607843, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/LINEN': value changed value in new API, from "Color(0.980392, 0.941176, 0.901961, 1)" to "Color(0.98039216, 0.9411765, 0.9019608, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MAROON': value changed value in new API, from "Color(0.690196, 0.188235, 0.376471, 1)" to "Color(0.6901961, 0.1882353, 0.3764706, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MEDIUM_AQUAMARINE': value changed value in new API, from "Color(0.4, 0.803922, 0.666667, 1)" to "Color(0.4, 0.8039216, 0.6666667, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MEDIUM_BLUE': value changed value in new API, from "Color(0, 0, 0.803922, 1)" to "Color(0, 0, 0.8039216, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MEDIUM_ORCHID': value changed value in new API, from "Color(0.729412, 0.333333, 0.827451, 1)" to "Color(0.7294118, 0.33333334, 0.827451, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MEDIUM_PURPLE': value changed value in new API, from "Color(0.576471, 0.439216, 0.858824, 1)" to "Color(0.5764706, 0.4392157, 0.85882354, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MEDIUM_SEA_GREEN': value changed value in new API, from "Color(0.235294, 0.701961, 0.443137, 1)" to "Color(0.23529412, 0.7019608, 0.44313726, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MEDIUM_SLATE_BLUE': value changed value in new API, from "Color(0.482353, 0.407843, 0.933333, 1)" to "Color(0.48235294, 0.40784314, 0.93333334, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MEDIUM_SPRING_GREEN': value changed value in new API, from "Color(0, 0.980392, 0.603922, 1)" to "Color(0, 0.98039216, 0.6039216, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MEDIUM_TURQUOISE': value changed value in new API, from "Color(0.282353, 0.819608, 0.8, 1)" to "Color(0.28235295, 0.81960785, 0.8, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MEDIUM_VIOLET_RED': value changed value in new API, from "Color(0.780392, 0.0823529, 0.521569, 1)" to "Color(0.78039217, 0.08235294, 0.52156866, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MIDNIGHT_BLUE': value changed value in new API, from "Color(0.0980392, 0.0980392, 0.439216, 1)" to "Color(0.09803922, 0.09803922, 0.4392157, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MINT_CREAM': value changed value in new API, from "Color(0.960784, 1, 0.980392, 1)" to "Color(0.9607843, 1, 0.98039216, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MISTY_ROSE': value changed value in new API, from "Color(1, 0.894118, 0.882353, 1)" to "Color(1, 0.89411765, 0.88235295, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/MOCCASIN': value changed value in new API, from "Color(1, 0.894118, 0.709804, 1)" to "Color(1, 0.89411765, 0.70980394, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/NAVAJO_WHITE': value changed value in new API, from "Color(1, 0.870588, 0.678431, 1)" to "Color(1, 0.87058824, 0.6784314, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/NAVY_BLUE': value changed value in new API, from "Color(0, 0, 0.501961, 1)" to "Color(0, 0, 0.5019608, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/OLD_LACE': value changed value in new API, from "Color(0.992157, 0.960784, 0.901961, 1)" to "Color(0.99215686, 0.9607843, 0.9019608, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/OLIVE': value changed value in new API, from "Color(0.501961, 0.501961, 0, 1)" to "Color(0.5019608, 0.5019608, 0, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/OLIVE_DRAB': value changed value in new API, from "Color(0.419608, 0.556863, 0.137255, 1)" to "Color(0.41960785, 0.5568628, 0.13725491, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/ORANGE': value changed value in new API, from "Color(1, 0.647059, 0, 1)" to "Color(1, 0.64705884, 0, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/ORANGE_RED': value changed value in new API, from "Color(1, 0.270588, 0, 1)" to "Color(1, 0.27058825, 0, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/ORCHID': value changed value in new API, from "Color(0.854902, 0.439216, 0.839216, 1)" to "Color(0.85490197, 0.4392157, 0.8392157, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/PALE_GOLDENROD': value changed value in new API, from "Color(0.933333, 0.909804, 0.666667, 1)" to "Color(0.93333334, 0.9098039, 0.6666667, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/PALE_GREEN': value changed value in new API, from "Color(0.596078, 0.984314, 0.596078, 1)" to "Color(0.59607846, 0.9843137, 0.59607846, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/PALE_TURQUOISE': value changed value in new API, from "Color(0.686275, 0.933333, 0.933333, 1)" to "Color(0.6862745, 0.93333334, 0.93333334, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/PALE_VIOLET_RED': value changed value in new API, from "Color(0.858824, 0.439216, 0.576471, 1)" to "Color(0.85882354, 0.4392157, 0.5764706, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/PAPAYA_WHIP': value changed value in new API, from "Color(1, 0.937255, 0.835294, 1)" to "Color(1, 0.9372549, 0.8352941, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/PEACH_PUFF': value changed value in new API, from "Color(1, 0.854902, 0.72549, 1)" to "Color(1, 0.85490197, 0.7254902, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/PERU': value changed value in new API, from "Color(0.803922, 0.521569, 0.247059, 1)" to "Color(0.8039216, 0.52156866, 0.24705882, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/PINK': value changed value in new API, from "Color(1, 0.752941, 0.796078, 1)" to "Color(1, 0.7529412, 0.79607844, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/PLUM': value changed value in new API, from "Color(0.866667, 0.627451, 0.866667, 1)" to "Color(0.8666667, 0.627451, 0.8666667, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/POWDER_BLUE': value changed value in new API, from "Color(0.690196, 0.878431, 0.901961, 1)" to "Color(0.6901961, 0.8784314, 0.9019608, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/PURPLE': value changed value in new API, from "Color(0.627451, 0.12549, 0.941176, 1)" to "Color(0.627451, 0.1254902, 0.9411765, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/ROSY_BROWN': value changed value in new API, from "Color(0.737255, 0.560784, 0.560784, 1)" to "Color(0.7372549, 0.56078434, 0.56078434, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/ROYAL_BLUE': value changed value in new API, from "Color(0.254902, 0.411765, 0.882353, 1)" to "Color(0.25490198, 0.4117647, 0.88235295, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/SADDLE_BROWN': value changed value in new API, from "Color(0.545098, 0.270588, 0.0745098, 1)" to "Color(0.54509807, 0.27058825, 0.07450981, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/SALMON': value changed value in new API, from "Color(0.980392, 0.501961, 0.447059, 1)" to "Color(0.98039216, 0.5019608, 0.44705883, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/SANDY_BROWN': value changed value in new API, from "Color(0.956863, 0.643137, 0.376471, 1)" to "Color(0.95686275, 0.6431373, 0.3764706, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/SEASHELL': value changed value in new API, from "Color(1, 0.960784, 0.933333, 1)" to "Color(1, 0.9607843, 0.93333334, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/SEA_GREEN': value changed value in new API, from "Color(0.180392, 0.545098, 0.341176, 1)" to "Color(0.18039216, 0.54509807, 0.34117648, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/SIENNA': value changed value in new API, from "Color(0.627451, 0.321569, 0.176471, 1)" to "Color(0.627451, 0.32156864, 0.1764706, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/SILVER': value changed value in new API, from "Color(0.752941, 0.752941, 0.752941, 1)" to "Color(0.7529412, 0.7529412, 0.7529412, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/SKY_BLUE': value changed value in new API, from "Color(0.529412, 0.807843, 0.921569, 1)" to "Color(0.5294118, 0.80784315, 0.92156863, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/SLATE_BLUE': value changed value in new API, from "Color(0.415686, 0.352941, 0.803922, 1)" to "Color(0.41568628, 0.3529412, 0.8039216, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/SLATE_GRAY': value changed value in new API, from "Color(0.439216, 0.501961, 0.564706, 1)" to "Color(0.4392157, 0.5019608, 0.5647059, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/SNOW': value changed value in new API, from "Color(1, 0.980392, 0.980392, 1)" to "Color(1, 0.98039216, 0.98039216, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/SPRING_GREEN': value changed value in new API, from "Color(0, 1, 0.498039, 1)" to "Color(0, 1, 0.49803922, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/STEEL_BLUE': value changed value in new API, from "Color(0.27451, 0.509804, 0.705882, 1)" to "Color(0.27450982, 0.50980395, 0.7058824, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/TAN': value changed value in new API, from "Color(0.823529, 0.705882, 0.54902, 1)" to "Color(0.8235294, 0.7058824, 0.54901963, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/TEAL': value changed value in new API, from "Color(0, 0.501961, 0.501961, 1)" to "Color(0, 0.5019608, 0.5019608, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/THISTLE': value changed value in new API, from "Color(0.847059, 0.74902, 0.847059, 1)" to "Color(0.84705883, 0.7490196, 0.84705883, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/TOMATO': value changed value in new API, from "Color(1, 0.388235, 0.278431, 1)" to "Color(1, 0.3882353, 0.2784314, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/TURQUOISE': value changed value in new API, from "Color(0.25098, 0.878431, 0.815686, 1)" to "Color(0.2509804, 0.8784314, 0.8156863, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/VIOLET': value changed value in new API, from "Color(0.933333, 0.509804, 0.933333, 1)" to "Color(0.93333334, 0.50980395, 0.93333334, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/WEB_GRAY': value changed value in new API, from "Color(0.501961, 0.501961, 0.501961, 1)" to "Color(0.5019608, 0.5019608, 0.5019608, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/WEB_GREEN': value changed value in new API, from "Color(0, 0.501961, 0, 1)" to "Color(0, 0.5019608, 0, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/WEB_MAROON': value changed value in new API, from "Color(0.501961, 0, 0, 1)" to "Color(0.5019608, 0, 0, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/WEB_PURPLE': value changed value in new API, from "Color(0.501961, 0, 0.501961, 1)" to "Color(0.5019608, 0, 0.5019608, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/WHEAT': value changed value in new API, from "Color(0.960784, 0.870588, 0.701961, 1)" to "Color(0.9607843, 0.87058824, 0.7019608, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/WHITE_SMOKE': value changed value in new API, from "Color(0.960784, 0.960784, 0.960784, 1)" to "Color(0.9607843, 0.9607843, 0.9607843, 1)".
+Validate extension JSON: Error: Field 'builtin_classes/Color/constants/YELLOW_GREEN': value changed value in new API, from "Color(0.603922, 0.803922, 0.196078, 1)" to "Color(0.6039216, 0.8039216, 0.19607843, 1)".
+Validate extension JSON: Error: Field 'classes/InputMap/methods/add_action/arguments/1': default_value changed value in new API, from "0.2" to "0.20000000298023224".
+Validate extension JSON: Error: Field 'classes/InputMap/methods/add_action/arguments/1': default_value changed value in new API, from "0.5" to "0.20000000298023224".
+Validate extension JSON: Error: Field 'global_enums/KeyModifierMask/values/KEY_MODIFIER_MASK': value changed value in new API, from 532676600.0 to 2130706432.
+
+Precision of string-serialized Variant constants increased.

+ 1 - 1
modules/gltf/doc_classes/GLTFCamera.xml

@@ -46,7 +46,7 @@
 		<member name="depth_near" type="float" setter="set_depth_near" getter="get_depth_near" default="0.05">
 			The distance to the near culling boundary for this camera relative to its local Z axis, in meters. This maps to glTF's [code]znear[/code] property.
 		</member>
-		<member name="fov" type="float" setter="set_fov" getter="get_fov" default="1.309">
+		<member name="fov" type="float" setter="set_fov" getter="get_fov" default="1.3089969">
 			The FOV of the camera. This class and glTF define the camera FOV in radians, while Godot uses degrees. This maps to glTF's [code]yfov[/code] property. This value is only used for perspective cameras, when [member perspective] is [code]true[/code].
 		</member>
 		<member name="perspective" type="bool" setter="set_perspective" getter="get_perspective" default="true">

+ 1 - 1
modules/gltf/doc_classes/GLTFLight.xml

@@ -66,7 +66,7 @@
 		<member name="light_type" type="String" setter="set_light_type" getter="get_light_type" default="&quot;&quot;">
 			The type of the light. The values accepted by Godot are "point", "spot", and "directional", which correspond to Godot's [OmniLight3D], [SpotLight3D], and [DirectionalLight3D] respectively.
 		</member>
-		<member name="outer_cone_angle" type="float" setter="set_outer_cone_angle" getter="get_outer_cone_angle" default="0.785398">
+		<member name="outer_cone_angle" type="float" setter="set_outer_cone_angle" getter="get_outer_cone_angle" default="0.7853982">
 			The outer angle of the cone in a spotlight. Must be greater than or equal to the inner angle.
 			At this angle, the light drops off to zero brightness. Between the inner and outer cone angles, there is a transition from full brightness to zero brightness. If this angle is a half turn, then the spotlight emits in all directions. When creating a Godot [SpotLight3D], the outer cone angle is used as the angle of the spotlight.
 		</member>

+ 1 - 1
modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml

@@ -12,7 +12,7 @@
 		<member name="aspect_ratio" type="float" setter="set_aspect_ratio" getter="get_aspect_ratio" default="1.0">
 			The aspect ratio of the slice. Used to set the height relative to the width.
 		</member>
-		<member name="central_angle" type="float" setter="set_central_angle" getter="get_central_angle" default="1.5708">
+		<member name="central_angle" type="float" setter="set_central_angle" getter="get_central_angle" default="1.5707964">
 			The central angle of the cylinder. Used to set the width.
 		</member>
 		<member name="fallback_segments" type="int" setter="set_fallback_segments" getter="get_fallback_segments" default="10">

+ 3 - 3
modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml

@@ -9,19 +9,19 @@
 	<tutorials>
 	</tutorials>
 	<members>
-		<member name="central_horizontal_angle" type="float" setter="set_central_horizontal_angle" getter="get_central_horizontal_angle" default="1.5708">
+		<member name="central_horizontal_angle" type="float" setter="set_central_horizontal_angle" getter="get_central_horizontal_angle" default="1.5707964">
 			The central horizontal angle of the sphere. Used to set the width.
 		</member>
 		<member name="fallback_segments" type="int" setter="set_fallback_segments" getter="get_fallback_segments" default="10">
 			The number of segments to use in the fallback mesh.
 		</member>
-		<member name="lower_vertical_angle" type="float" setter="set_lower_vertical_angle" getter="get_lower_vertical_angle" default="0.785398">
+		<member name="lower_vertical_angle" type="float" setter="set_lower_vertical_angle" getter="get_lower_vertical_angle" default="0.7853982">
 			The lower vertical angle of the sphere. Used (together with [member upper_vertical_angle]) to set the height.
 		</member>
 		<member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0">
 			The radius of the sphere.
 		</member>
-		<member name="upper_vertical_angle" type="float" setter="set_upper_vertical_angle" getter="get_upper_vertical_angle" default="0.785398">
+		<member name="upper_vertical_angle" type="float" setter="set_upper_vertical_angle" getter="get_upper_vertical_angle" default="0.7853982">
 			The upper vertical angle of the sphere. Used (together with [member lower_vertical_angle]) to set the height.
 		</member>
 	</members>

+ 1 - 1
modules/openxr/doc_classes/OpenXRDpadBindingModifier.xml

@@ -36,7 +36,7 @@
 		<member name="threshold_released" type="float" setter="set_threshold_released" getter="get_threshold_released" default="0.4">
 			When our input value falls below this, our output becomes [code]false[/code].
 		</member>
-		<member name="wedge_angle" type="float" setter="set_wedge_angle" getter="get_wedge_angle" default="1.5708">
+		<member name="wedge_angle" type="float" setter="set_wedge_angle" getter="get_wedge_angle" default="1.5707964">
 			The angle of each wedge that identifies the 4 directions of the emulated dpad.
 		</member>
 	</members>

+ 14 - 1
tests/core/string/test_string.h

@@ -546,7 +546,6 @@ TEST_CASE("[String] Number to string") {
 	CHECK(String::num(3.141593) == "3.141593");
 	CHECK(String::num(3.141593, 3) == "3.142");
 	CHECK(String::num(42.100023, 4) == "42.1"); // No trailing zeros.
-	CHECK(String::num_scientific(30000000) == "3e+07");
 
 	// String::num_int64 tests.
 	CHECK(String::num_int64(3141593) == "3141593");
@@ -567,6 +566,20 @@ TEST_CASE("[String] Number to string") {
 	CHECK(String::num_uint64(4294967295, 37) == ""); // Invalid base > 36.
 	ERR_PRINT_ON;
 
+	// String::num_scientific tests.
+	CHECK(String::num_scientific(30000000.0) == "30000000");
+	CHECK(String::num_scientific(1234567890.0) == "1234567890");
+	CHECK(String::num_scientific(3e100) == "3e+100");
+	CHECK(String::num_scientific(7e-100) == "7e-100");
+	CHECK(String::num_scientific(Math::TAU) == "6.283185307179586");
+	CHECK(String::num_scientific(Math::INF) == "inf");
+	CHECK(String::num_scientific(-Math::INF) == "-inf");
+	CHECK(String::num_scientific(Math::NaN) == "nan");
+	CHECK(String::num_scientific(2.0) == "2");
+	CHECK(String::num_scientific(1.0) == "1");
+	CHECK(String::num_scientific(0.0) == "0");
+	CHECK(String::num_scientific(-0.0) == "-0");
+
 	// String::num_real tests.
 	CHECK(String::num_real(1.0) == "1.0");
 	CHECK(String::num_real(1.0, false) == "1");

+ 34 - 2
tests/core/variant/test_variant.h

@@ -101,7 +101,7 @@ TEST_CASE("[Variant] Writer and parser Variant::FLOAT") {
 	String a64_str;
 	VariantWriter::write_to_string(a64, a64_str);
 
-	CHECK_MESSAGE(a64_str == "1.79769e+308", "Writes in scientific notation.");
+	CHECK_MESSAGE(a64_str == "1.7976931348623157e+308", "Writes in scientific notation.");
 	CHECK_MESSAGE(a64_str != "inf", "Should not overflow.");
 	CHECK_MESSAGE(a64_str != "nan", "The result should be defined.");
 
@@ -115,7 +115,7 @@ TEST_CASE("[Variant] Writer and parser Variant::FLOAT") {
 	VariantParser::parse(&bss, variant_parsed, errs, line);
 	float_parsed = variant_parsed;
 	// Loses precision, but that's alright.
-	CHECK_MESSAGE(float_parsed == 1.79769e+308, "Should parse back.");
+	CHECK_MESSAGE(float_parsed == 1.797693134862315708145274237317e+308, "Should parse back.");
 
 	// Approximation of Googol with a double-precision float.
 	VariantParser::StreamString css;
@@ -1754,6 +1754,38 @@ TEST_CASE("[Variant] array initializer list") {
 	CHECK(packed_arr[2] == 0);
 }
 
+TEST_CASE("[Variant] Writer and parser Vector2") {
+	Variant vec2_parsed;
+	String vec2_str;
+	String errs;
+	int line;
+	// Variant::VECTOR2 and Vector2 can be either 32-bit or 64-bit depending on the precision level of real_t.
+	{
+		Vector2 vec2 = Vector2(1.2, 3.4);
+		VariantWriter::write_to_string(vec2, vec2_str);
+		// Reminder: "1.2" and "3.4" are not exactly those decimal numbers. They are the closest float to them.
+		CHECK_MESSAGE(vec2_str == "Vector2(1.2, 3.4)", "Should write with enough digits to ensure parsing back is exact.");
+		VariantParser::StreamString stream;
+		stream.s = vec2_str;
+		VariantParser::parse(&stream, vec2_parsed, errs, line);
+		CHECK_MESSAGE(Vector2(vec2_parsed) == vec2, "Should parse back to the same Vector2.");
+	}
+	// Check with big numbers and small numbers.
+	{
+		Vector2 vec2 = Vector2(1.234567898765432123456789e30, 1.234567898765432123456789e-10);
+		VariantWriter::write_to_string(vec2, vec2_str);
+#ifdef REAL_T_IS_DOUBLE
+		CHECK_MESSAGE(vec2_str == "Vector2(1.2345678987654322e+30, 1.2345678987654322e-10)", "Should write with enough digits to ensure parsing back is exact.");
+#else
+		CHECK_MESSAGE(vec2_str == "Vector2(1.2345679e+30, 1.2345679e-10)", "Should write with enough digits to ensure parsing back is exact.");
+#endif
+		VariantParser::StreamString stream;
+		stream.s = vec2_str;
+		VariantParser::parse(&stream, vec2_parsed, errs, line);
+		CHECK_MESSAGE(Vector2(vec2_parsed) == vec2, "Should parse back to the same Vector2.");
+	}
+}
+
 TEST_CASE("[Variant] Writer and parser array") {
 	Array a = build_array(1, String("hello"), build_array(Variant()));
 	String a_str;

+ 15 - 0
thirdparty/README.md

@@ -415,6 +415,21 @@ Files extracted from upstream source:
 - `COPYING`
 
 
+## grisu2
+
+- Upstream: https://github.com/simdjson/simdjson/blob/master/src/to_chars.cpp
+- Version: git (4f4e81668ecb9d4d37fd5f59a1556d492507421d, 2023)
+- License: Apache and MIT
+
+Files extracted from upstream source:
+
+- The `src/to_chars.cpp` file renamed to `grisu2.h` and slightly modified.
+
+Patches:
+
+- `0001-godot-changes.patch` (GH-98750)
+
+
 ## harfbuzz
 
 - Upstream: https://github.com/harfbuzz/harfbuzz

+ 47 - 0
thirdparty/grisu2/LICENSE

@@ -0,0 +1,47 @@
+The Grisu2 algorithm is by Florian Loitsch, based on the work of Robert G. Burger and R. Kent Dybvig:
+
+    [1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming Language Design and Implementation, PLDI 2010
+    [2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language Design and Implementation, PLDI 1996
+
+
+The original C implementation is by Florian Loitsch:
+
+    Copyright (c) 2009 Florian Loitsch
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation
+    files (the "Software"), to deal in the Software without
+    restriction, including without limitation the rights to use,
+    copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the
+    Software is furnished to do so, subject to the following
+    conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+    OTHER DEALINGS IN THE SOFTWARE.
+
+
+The implementation simplified and adapted to JSON and C++11 by Daniel Lemire as part of simdjson:
+
+    Copyright 2018-2023 The simdjson authors
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.

+ 20 - 0
thirdparty/grisu2/README.md

@@ -0,0 +1,20 @@
+# Grisu2
+
+This is a C++11 implementation of the Grisu2 algorithm for converting floating-point numbers to decimal strings.
+
+The Grisu2 algorithm is by Florian Loitsch, based on the work of Robert G. Burger and R. Kent Dybvig:
+
+- https://dl.acm.org/doi/10.1145/1806596.1806623 [1] Loitsch, "Printing Floating-Point Numbers Quickly and Accurately with Integers", Proceedings of the ACM SIGPLAN 2010 Conference on Programming Language Design and Implementation, PLDI 2010
+- https://dl.acm.org/doi/10.1145/231379.231397 [2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and Accurately", Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language Design and Implementation, PLDI 1996
+
+The original C implementation is by Florian Loitsch:
+- https://drive.google.com/file/d/0BwvYOx00EwKmejFIMjRORTFLcTA/view?resourcekey=0-1Lg8tXTC_JAODUcFpMcaTA
+
+The implementation simplified and adapted to JSON and C++11 by Daniel Lemire as part of simdjson:
+- https://github.com/simdjson/simdjson/blob/master/src/to_chars.cpp
+
+The `grisu2.h` file is the same as `to_chars.cpp` but with `godot.patch` applied to it, with the following changes:
+- Simplify namespaces to just be one `grisu2` namespace.
+- Rename functions to ensure their names are unique.
+- Make `to_chars` handle both float and double types instead of just double.
+- Remove the trailing `.0` logic to match Godot's existing `String::num_scientific` behavior.

+ 936 - 0
thirdparty/grisu2/grisu2.h

@@ -0,0 +1,936 @@
+#pragma once
+
+#include <cstring>
+#include <cstdint>
+#include <array>
+#include <cmath>
+
+namespace grisu2 {
+/*!
+implements the Grisu2 algorithm for binary to decimal floating-point
+conversion.
+Adapted from JSON for Modern C++
+
+This implementation is a slightly modified version of the reference
+implementation which may be obtained from
+http://florian.loitsch.com/publications (bench.tar.gz).
+The code is distributed under the MIT license, Copyright (c) 2009 Florian
+Loitsch. For a detailed description of the algorithm see: [1] Loitsch, "Printing
+Floating-Point Numbers Quickly and Accurately with Integers", Proceedings of the
+ACM SIGPLAN 2010 Conference on Programming Language Design and Implementation,
+PLDI 2010 [2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and
+Accurately", Proceedings of the ACM SIGPLAN 1996 Conference on Programming
+Language Design and Implementation, PLDI 1996
+*/
+
+template <typename Target, typename Source>
+Target reinterpret_bits(const Source source) {
+  static_assert(sizeof(Target) == sizeof(Source), "size mismatch");
+
+  Target target;
+  std::memcpy(&target, &source, sizeof(Source));
+  return target;
+}
+
+struct diyfp // f * 2^e
+{
+  static constexpr int kPrecision = 64; // = q
+
+  std::uint64_t f = 0;
+  int e = 0;
+
+  constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {}
+
+  /*!
+  @brief returns x - y
+  @pre x.e == y.e and x.f >= y.f
+  */
+  static diyfp sub(const diyfp &x, const diyfp &y) noexcept {
+
+    return {x.f - y.f, x.e};
+  }
+
+  /*!
+  @brief returns x * y
+  @note The result is rounded. (Only the upper q bits are returned.)
+  */
+  static diyfp mul(const diyfp &x, const diyfp &y) noexcept {
+    static_assert(kPrecision == 64, "internal error");
+
+    // Computes:
+    //  f = round((x.f * y.f) / 2^q)
+    //  e = x.e + y.e + q
+
+    // Emulate the 64-bit * 64-bit multiplication:
+    //
+    // p = u * v
+    //   = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi)
+    //   = (u_lo v_lo         ) + 2^32 ((u_lo v_hi         ) + (u_hi v_lo )) +
+    //   2^64 (u_hi v_hi         ) = (p0                ) + 2^32 ((p1 ) + (p2 ))
+    //   + 2^64 (p3                ) = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo +
+    //   2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3                ) =
+    //   (p0_lo             ) + 2^32 (p0_hi + p1_lo + p2_lo ) + 2^64 (p1_hi +
+    //   p2_hi + p3) = (p0_lo             ) + 2^32 (Q ) + 2^64 (H ) = (p0_lo ) +
+    //   2^32 (Q_lo + 2^32 Q_hi                           ) + 2^64 (H )
+    //
+    // (Since Q might be larger than 2^32 - 1)
+    //
+    //   = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H)
+    //
+    // (Q_hi + H does not overflow a 64-bit int)
+    //
+    //   = p_lo + 2^64 p_hi
+
+    const std::uint64_t u_lo = x.f & 0xFFFFFFFFu;
+    const std::uint64_t u_hi = x.f >> 32u;
+    const std::uint64_t v_lo = y.f & 0xFFFFFFFFu;
+    const std::uint64_t v_hi = y.f >> 32u;
+
+    const std::uint64_t p0 = u_lo * v_lo;
+    const std::uint64_t p1 = u_lo * v_hi;
+    const std::uint64_t p2 = u_hi * v_lo;
+    const std::uint64_t p3 = u_hi * v_hi;
+
+    const std::uint64_t p0_hi = p0 >> 32u;
+    const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu;
+    const std::uint64_t p1_hi = p1 >> 32u;
+    const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu;
+    const std::uint64_t p2_hi = p2 >> 32u;
+
+    std::uint64_t Q = p0_hi + p1_lo + p2_lo;
+
+    // The full product might now be computed as
+    //
+    // p_hi = p3 + p2_hi + p1_hi + (Q >> 32)
+    // p_lo = p0_lo + (Q << 32)
+    //
+    // But in this particular case here, the full p_lo is not required.
+    // Effectively we only need to add the highest bit in p_lo to p_hi (and
+    // Q_hi + 1 does not overflow).
+
+    Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up
+
+    const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u);
+
+    return {h, x.e + y.e + 64};
+  }
+
+  /*!
+  @brief normalize x such that the significand is >= 2^(q-1)
+  @pre x.f != 0
+  */
+  static diyfp normalize(diyfp x) noexcept {
+
+    while ((x.f >> 63u) == 0) {
+      x.f <<= 1u;
+      x.e--;
+    }
+
+    return x;
+  }
+
+  /*!
+  @brief normalize x such that the result has the exponent E
+  @pre e >= x.e and the upper e - x.e bits of x.f must be zero.
+  */
+  static diyfp normalize_to(const diyfp &x,
+                            const int target_exponent) noexcept {
+    const int delta = x.e - target_exponent;
+
+    return {x.f << delta, target_exponent};
+  }
+};
+
+struct boundaries {
+  diyfp w;
+  diyfp minus;
+  diyfp plus;
+};
+
+/*!
+Compute the (normalized) diyfp representing the input number 'value' and its
+boundaries.
+@pre value must be finite and positive
+*/
+template <typename FloatType> boundaries compute_boundaries(FloatType value) {
+
+  // Convert the IEEE representation into a diyfp.
+  //
+  // If v is denormal:
+  //      value = 0.F * 2^(1 - bias) = (          F) * 2^(1 - bias - (p-1))
+  // If v is normalized:
+  //      value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1))
+
+  static_assert(std::numeric_limits<FloatType>::is_iec559,
+                "internal error: dtoa_short requires an IEEE-754 "
+                "floating-point implementation");
+
+  constexpr int kPrecision =
+      std::numeric_limits<FloatType>::digits; // = p (includes the hidden bit)
+  constexpr int kBias =
+      std::numeric_limits<FloatType>::max_exponent - 1 + (kPrecision - 1);
+  constexpr int kMinExp = 1 - kBias;
+  constexpr std::uint64_t kHiddenBit = std::uint64_t{1}
+                                       << (kPrecision - 1); // = 2^(p-1)
+
+  using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t,
+                                              std::uint64_t>::type;
+
+  const std::uint64_t bits = reinterpret_bits<bits_type>(value);
+  const std::uint64_t E = bits >> (kPrecision - 1);
+  const std::uint64_t F = bits & (kHiddenBit - 1);
+
+  const bool is_denormal = E == 0;
+  const diyfp v = is_denormal
+                      ? diyfp(F, kMinExp)
+                      : diyfp(F + kHiddenBit, static_cast<int>(E) - kBias);
+
+  // Compute the boundaries m- and m+ of the floating-point value
+  // v = f * 2^e.
+  //
+  // Determine v- and v+, the floating-point predecessor and successor if v,
+  // respectively.
+  //
+  //      v- = v - 2^e        if f != 2^(p-1) or e == e_min                (A)
+  //         = v - 2^(e-1)    if f == 2^(p-1) and e > e_min                (B)
+  //
+  //      v+ = v + 2^e
+  //
+  // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_
+  // between m- and m+ round to v, regardless of how the input rounding
+  // algorithm breaks ties.
+  //
+  //      ---+-------------+-------------+-------------+-------------+---  (A)
+  //         v-            m-            v             m+            v+
+  //
+  //      -----------------+------+------+-------------+-------------+---  (B)
+  //                       v-     m-     v             m+            v+
+
+  const bool lower_boundary_is_closer = F == 0 && E > 1;
+  const diyfp m_plus = diyfp(2 * v.f + 1, v.e - 1);
+  const diyfp m_minus = lower_boundary_is_closer
+                            ? diyfp(4 * v.f - 1, v.e - 2)  // (B)
+                            : diyfp(2 * v.f - 1, v.e - 1); // (A)
+
+  // Determine the normalized w+ = m+.
+  const diyfp w_plus = diyfp::normalize(m_plus);
+
+  // Determine w- = m- such that e_(w-) = e_(w+).
+  const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e);
+
+  return {diyfp::normalize(v), w_minus, w_plus};
+}
+
+// Given normalized diyfp w, Grisu needs to find a (normalized) cached
+// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies
+// within a certain range [alpha, gamma] (Definition 3.2 from [1])
+//
+//      alpha <= e = e_c + e_w + q <= gamma
+//
+// or
+//
+//      f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q
+//                          <= f_c * f_w * 2^gamma
+//
+// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies
+//
+//      2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma
+//
+// or
+//
+//      2^(q - 2 + alpha) <= c * w < 2^(q + gamma)
+//
+// The choice of (alpha,gamma) determines the size of the table and the form of
+// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well
+// in practice:
+//
+// The idea is to cut the number c * w = f * 2^e into two parts, which can be
+// processed independently: An integral part p1, and a fractional part p2:
+//
+//      f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e
+//              = (f div 2^-e) + (f mod 2^-e) * 2^e
+//              = p1 + p2 * 2^e
+//
+// The conversion of p1 into decimal form requires a series of divisions and
+// modulos by (a power of) 10. These operations are faster for 32-bit than for
+// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be
+// achieved by choosing
+//
+//      -e >= 32   or   e <= -32 := gamma
+//
+// In order to convert the fractional part
+//
+//      p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ...
+//
+// into decimal form, the fraction is repeatedly multiplied by 10 and the digits
+// d[-i] are extracted in order:
+//
+//      (10 * p2) div 2^-e = d[-1]
+//      (10 * p2) mod 2^-e = d[-2] / 10^1 + ...
+//
+// The multiplication by 10 must not overflow. It is sufficient to choose
+//
+//      10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64.
+//
+// Since p2 = f mod 2^-e < 2^-e,
+//
+//      -e <= 60   or   e >= -60 := alpha
+
+constexpr int kAlpha = -60;
+constexpr int kGamma = -32;
+
+struct cached_power // c = f * 2^e ~= 10^k
+{
+  std::uint64_t f;
+  int e;
+  int k;
+};
+
+/*!
+For a normalized diyfp w = f * 2^e, this function returns a (normalized) cached
+power-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c
+satisfies (Definition 3.2 from [1])
+     alpha <= e_c + e + q <= gamma.
+*/
+inline cached_power get_cached_power_for_binary_exponent(int e) {
+  // Now
+  //
+  //      alpha <= e_c + e + q <= gamma                                    (1)
+  //      ==> f_c * 2^alpha <= c * 2^e * 2^q
+  //
+  // and since the c's are normalized, 2^(q-1) <= f_c,
+  //
+  //      ==> 2^(q - 1 + alpha) <= c * 2^(e + q)
+  //      ==> 2^(alpha - e - 1) <= c
+  //
+  // If c were an exact power of ten, i.e. c = 10^k, one may determine k as
+  //
+  //      k = ceil( log_10( 2^(alpha - e - 1) ) )
+  //        = ceil( (alpha - e - 1) * log_10(2) )
+  //
+  // From the paper:
+  // "In theory the result of the procedure could be wrong since c is rounded,
+  //  and the computation itself is approximated [...]. In practice, however,
+  //  this simple function is sufficient."
+  //
+  // For IEEE double precision floating-point numbers converted into
+  // normalized diyfp's w = f * 2^e, with q = 64,
+  //
+  //      e >= -1022      (min IEEE exponent)
+  //           -52        (p - 1)
+  //           -52        (p - 1, possibly normalize denormal IEEE numbers)
+  //           -11        (normalize the diyfp)
+  //         = -1137
+  //
+  // and
+  //
+  //      e <= +1023      (max IEEE exponent)
+  //           -52        (p - 1)
+  //           -11        (normalize the diyfp)
+  //         = 960
+  //
+  // This binary exponent range [-1137,960] results in a decimal exponent
+  // range [-307,324]. One does not need to store a cached power for each
+  // k in this range. For each such k it suffices to find a cached power
+  // such that the exponent of the product lies in [alpha,gamma].
+  // This implies that the difference of the decimal exponents of adjacent
+  // table entries must be less than or equal to
+  //
+  //      floor( (gamma - alpha) * log_10(2) ) = 8.
+  //
+  // (A smaller distance gamma-alpha would require a larger table.)
+
+  // NB:
+  // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34.
+
+  constexpr int kCachedPowersMinDecExp = -300;
+  constexpr int kCachedPowersDecStep = 8;
+
+  static constexpr std::array<cached_power, 79> kCachedPowers = {{
+      {0xAB70FE17C79AC6CA, -1060, -300}, {0xFF77B1FCBEBCDC4F, -1034, -292},
+      {0xBE5691EF416BD60C, -1007, -284}, {0x8DD01FAD907FFC3C, -980, -276},
+      {0xD3515C2831559A83, -954, -268},  {0x9D71AC8FADA6C9B5, -927, -260},
+      {0xEA9C227723EE8BCB, -901, -252},  {0xAECC49914078536D, -874, -244},
+      {0x823C12795DB6CE57, -847, -236},  {0xC21094364DFB5637, -821, -228},
+      {0x9096EA6F3848984F, -794, -220},  {0xD77485CB25823AC7, -768, -212},
+      {0xA086CFCD97BF97F4, -741, -204},  {0xEF340A98172AACE5, -715, -196},
+      {0xB23867FB2A35B28E, -688, -188},  {0x84C8D4DFD2C63F3B, -661, -180},
+      {0xC5DD44271AD3CDBA, -635, -172},  {0x936B9FCEBB25C996, -608, -164},
+      {0xDBAC6C247D62A584, -582, -156},  {0xA3AB66580D5FDAF6, -555, -148},
+      {0xF3E2F893DEC3F126, -529, -140},  {0xB5B5ADA8AAFF80B8, -502, -132},
+      {0x87625F056C7C4A8B, -475, -124},  {0xC9BCFF6034C13053, -449, -116},
+      {0x964E858C91BA2655, -422, -108},  {0xDFF9772470297EBD, -396, -100},
+      {0xA6DFBD9FB8E5B88F, -369, -92},   {0xF8A95FCF88747D94, -343, -84},
+      {0xB94470938FA89BCF, -316, -76},   {0x8A08F0F8BF0F156B, -289, -68},
+      {0xCDB02555653131B6, -263, -60},   {0x993FE2C6D07B7FAC, -236, -52},
+      {0xE45C10C42A2B3B06, -210, -44},   {0xAA242499697392D3, -183, -36},
+      {0xFD87B5F28300CA0E, -157, -28},   {0xBCE5086492111AEB, -130, -20},
+      {0x8CBCCC096F5088CC, -103, -12},   {0xD1B71758E219652C, -77, -4},
+      {0x9C40000000000000, -50, 4},      {0xE8D4A51000000000, -24, 12},
+      {0xAD78EBC5AC620000, 3, 20},       {0x813F3978F8940984, 30, 28},
+      {0xC097CE7BC90715B3, 56, 36},      {0x8F7E32CE7BEA5C70, 83, 44},
+      {0xD5D238A4ABE98068, 109, 52},     {0x9F4F2726179A2245, 136, 60},
+      {0xED63A231D4C4FB27, 162, 68},     {0xB0DE65388CC8ADA8, 189, 76},
+      {0x83C7088E1AAB65DB, 216, 84},     {0xC45D1DF942711D9A, 242, 92},
+      {0x924D692CA61BE758, 269, 100},    {0xDA01EE641A708DEA, 295, 108},
+      {0xA26DA3999AEF774A, 322, 116},    {0xF209787BB47D6B85, 348, 124},
+      {0xB454E4A179DD1877, 375, 132},    {0x865B86925B9BC5C2, 402, 140},
+      {0xC83553C5C8965D3D, 428, 148},    {0x952AB45CFA97A0B3, 455, 156},
+      {0xDE469FBD99A05FE3, 481, 164},    {0xA59BC234DB398C25, 508, 172},
+      {0xF6C69A72A3989F5C, 534, 180},    {0xB7DCBF5354E9BECE, 561, 188},
+      {0x88FCF317F22241E2, 588, 196},    {0xCC20CE9BD35C78A5, 614, 204},
+      {0x98165AF37B2153DF, 641, 212},    {0xE2A0B5DC971F303A, 667, 220},
+      {0xA8D9D1535CE3B396, 694, 228},    {0xFB9B7CD9A4A7443C, 720, 236},
+      {0xBB764C4CA7A44410, 747, 244},    {0x8BAB8EEFB6409C1A, 774, 252},
+      {0xD01FEF10A657842C, 800, 260},    {0x9B10A4E5E9913129, 827, 268},
+      {0xE7109BFBA19C0C9D, 853, 276},    {0xAC2820D9623BF429, 880, 284},
+      {0x80444B5E7AA7CF85, 907, 292},    {0xBF21E44003ACDD2D, 933, 300},
+      {0x8E679C2F5E44FF8F, 960, 308},    {0xD433179D9C8CB841, 986, 316},
+      {0x9E19DB92B4E31BA9, 1013, 324},
+  }};
+
+  // This computation gives exactly the same results for k as
+  //      k = ceil((kAlpha - e - 1) * 0.30102999566398114)
+  // for |e| <= 1500, but doesn't require floating-point operations.
+  // NB: log_10(2) ~= 78913 / 2^18
+  const int f = kAlpha - e - 1;
+  const int k = (f * 78913) / (1 << 18) + static_cast<int>(f > 0);
+
+  const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) /
+                    kCachedPowersDecStep;
+
+  const cached_power cached = kCachedPowers[static_cast<std::size_t>(index)];
+
+  return cached;
+}
+
+/*!
+For n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k.
+For n == 0, returns 1 and sets pow10 := 1.
+*/
+inline int find_largest_pow10(const std::uint32_t n, std::uint32_t &pow10) {
+  // LCOV_EXCL_START
+  if (n >= 1000000000) {
+    pow10 = 1000000000;
+    return 10;
+  }
+  // LCOV_EXCL_STOP
+  else if (n >= 100000000) {
+    pow10 = 100000000;
+    return 9;
+  } else if (n >= 10000000) {
+    pow10 = 10000000;
+    return 8;
+  } else if (n >= 1000000) {
+    pow10 = 1000000;
+    return 7;
+  } else if (n >= 100000) {
+    pow10 = 100000;
+    return 6;
+  } else if (n >= 10000) {
+    pow10 = 10000;
+    return 5;
+  } else if (n >= 1000) {
+    pow10 = 1000;
+    return 4;
+  } else if (n >= 100) {
+    pow10 = 100;
+    return 3;
+  } else if (n >= 10) {
+    pow10 = 10;
+    return 2;
+  } else {
+    pow10 = 1;
+    return 1;
+  }
+}
+
+inline void grisu2_round(char *buf, int len, std::uint64_t dist,
+                         std::uint64_t delta, std::uint64_t rest,
+                         std::uint64_t ten_k) {
+
+  //               <--------------------------- delta ---->
+  //                                  <---- dist --------->
+  // --------------[------------------+-------------------]--------------
+  //               M-                 w                   M+
+  //
+  //                                  ten_k
+  //                                <------>
+  //                                       <---- rest ---->
+  // --------------[------------------+----+--------------]--------------
+  //                                  w    V
+  //                                       = buf * 10^k
+  //
+  // ten_k represents a unit-in-the-last-place in the decimal representation
+  // stored in buf.
+  // Decrement buf by ten_k while this takes buf closer to w.
+
+  // The tests are written in this order to avoid overflow in unsigned
+  // integer arithmetic.
+
+  while (rest < dist && delta - rest >= ten_k &&
+         (rest + ten_k < dist || dist - rest > rest + ten_k - dist)) {
+    buf[len - 1]--;
+    rest += ten_k;
+  }
+}
+
+/*!
+Generates V = buffer * 10^decimal_exponent, such that M- <= V <= M+.
+M- and M+ must be normalized and share the same exponent -60 <= e <= -32.
+*/
+inline void grisu2_digit_gen(char *buffer, int &length, int &decimal_exponent,
+                             diyfp M_minus, diyfp w, diyfp M_plus) {
+  static_assert(kAlpha >= -60, "internal error");
+  static_assert(kGamma <= -32, "internal error");
+
+  // Generates the digits (and the exponent) of a decimal floating-point
+  // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's
+  // w, M- and M+ share the same exponent e, which satisfies alpha <= e <=
+  // gamma.
+  //
+  //               <--------------------------- delta ---->
+  //                                  <---- dist --------->
+  // --------------[------------------+-------------------]--------------
+  //               M-                 w                   M+
+  //
+  // Grisu2 generates the digits of M+ from left to right and stops as soon as
+  // V is in [M-,M+].
+
+  std::uint64_t delta =
+      diyfp::sub(M_plus, M_minus)
+          .f; // (significand of (M+ - M-), implicit exponent is e)
+  std::uint64_t dist =
+      diyfp::sub(M_plus, w)
+          .f; // (significand of (M+ - w ), implicit exponent is e)
+
+  // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0):
+  //
+  //      M+ = f * 2^e
+  //         = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e
+  //         = ((p1        ) * 2^-e + (p2        )) * 2^e
+  //         = p1 + p2 * 2^e
+
+  const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e);
+
+  auto p1 = static_cast<std::uint32_t>(
+      M_plus.f >>
+      -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.)
+  std::uint64_t p2 = M_plus.f & (one.f - 1); // p2 = f mod 2^-e
+
+  // 1)
+  //
+  // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0]
+
+  std::uint32_t pow10;
+  const int k = find_largest_pow10(p1, pow10);
+
+  //      10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1)
+  //
+  //      p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1))
+  //         = (d[k-1]         ) * 10^(k-1) + (p1 mod 10^(k-1))
+  //
+  //      M+ = p1                                             + p2 * 2^e
+  //         = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1))          + p2 * 2^e
+  //         = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e
+  //         = d[k-1] * 10^(k-1) + (                         rest) * 2^e
+  //
+  // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0)
+  //
+  //      p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0]
+  //
+  // but stop as soon as
+  //
+  //      rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e
+
+  int n = k;
+  while (n > 0) {
+    // Invariants:
+    //      M+ = buffer * 10^n + (p1 + p2 * 2^e)    (buffer = 0 for n = k)
+    //      pow10 = 10^(n-1) <= p1 < 10^n
+    //
+    const std::uint32_t d = p1 / pow10; // d = p1 div 10^(n-1)
+    const std::uint32_t r = p1 % pow10; // r = p1 mod 10^(n-1)
+    //
+    //      M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e
+    //         = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e)
+    //
+    buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d
+    //
+    //      M+ = buffer * 10^(n-1) + (r + p2 * 2^e)
+    //
+    p1 = r;
+    n--;
+    //
+    //      M+ = buffer * 10^n + (p1 + p2 * 2^e)
+    //      pow10 = 10^n
+    //
+
+    // Now check if enough digits have been generated.
+    // Compute
+    //
+    //      p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e
+    //
+    // Note:
+    // Since rest and delta share the same exponent e, it suffices to
+    // compare the significands.
+    const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2;
+    if (rest <= delta) {
+      // V = buffer * 10^n, with M- <= V <= M+.
+
+      decimal_exponent += n;
+
+      // We may now just stop. But instead look if the buffer could be
+      // decremented to bring V closer to w.
+      //
+      // pow10 = 10^n is now 1 ulp in the decimal representation V.
+      // The rounding procedure works with diyfp's with an implicit
+      // exponent of e.
+      //
+      //      10^n = (10^n * 2^-e) * 2^e = ulp * 2^e
+      //
+      const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e;
+      grisu2_round(buffer, length, dist, delta, rest, ten_n);
+
+      return;
+    }
+
+    pow10 /= 10;
+    //
+    //      pow10 = 10^(n-1) <= p1 < 10^n
+    // Invariants restored.
+  }
+
+  // 2)
+  //
+  // The digits of the integral part have been generated:
+  //
+  //      M+ = d[k-1]...d[1]d[0] + p2 * 2^e
+  //         = buffer            + p2 * 2^e
+  //
+  // Now generate the digits of the fractional part p2 * 2^e.
+  //
+  // Note:
+  // No decimal point is generated: the exponent is adjusted instead.
+  //
+  // p2 actually represents the fraction
+  //
+  //      p2 * 2^e
+  //          = p2 / 2^-e
+  //          = d[-1] / 10^1 + d[-2] / 10^2 + ...
+  //
+  // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...)
+  //
+  //      p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m
+  //                      + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...)
+  //
+  // using
+  //
+  //      10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e)
+  //                = (                   d) * 2^-e + (                   r)
+  //
+  // or
+  //      10^m * p2 * 2^e = d + r * 2^e
+  //
+  // i.e.
+  //
+  //      M+ = buffer + p2 * 2^e
+  //         = buffer + 10^-m * (d + r * 2^e)
+  //         = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e
+  //
+  // and stop as soon as 10^-m * r * 2^e <= delta * 2^e
+
+  int m = 0;
+  for (;;) {
+    // Invariant:
+    //      M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...)
+    //      * 2^e
+    //         = buffer * 10^-m + 10^-m * (p2                                 )
+    //         * 2^e = buffer * 10^-m + 10^-m * (1/10 * (10 * p2) ) * 2^e =
+    //         buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e +
+    //         (10*p2 mod 2^-e)) * 2^e
+    //
+    p2 *= 10;
+    const std::uint64_t d = p2 >> -one.e;     // d = (10 * p2) div 2^-e
+    const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e
+    //
+    //      M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e
+    //         = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e))
+    //         = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e
+    //
+    buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d
+    //
+    //      M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e
+    //
+    p2 = r;
+    m++;
+    //
+    //      M+ = buffer * 10^-m + 10^-m * p2 * 2^e
+    // Invariant restored.
+
+    // Check if enough digits have been generated.
+    //
+    //      10^-m * p2 * 2^e <= delta * 2^e
+    //              p2 * 2^e <= 10^m * delta * 2^e
+    //                    p2 <= 10^m * delta
+    delta *= 10;
+    dist *= 10;
+    if (p2 <= delta) {
+      break;
+    }
+  }
+
+  // V = buffer * 10^-m, with M- <= V <= M+.
+
+  decimal_exponent -= m;
+
+  // 1 ulp in the decimal representation is now 10^-m.
+  // Since delta and dist are now scaled by 10^m, we need to do the
+  // same with ulp in order to keep the units in sync.
+  //
+  //      10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e
+  //
+  const std::uint64_t ten_m = one.f;
+  grisu2_round(buffer, length, dist, delta, p2, ten_m);
+
+  // By construction this algorithm generates the shortest possible decimal
+  // number (Loitsch, Theorem 6.2) which rounds back to w.
+  // For an input number of precision p, at least
+  //
+  //      N = 1 + ceil(p * log_10(2))
+  //
+  // decimal digits are sufficient to identify all binary floating-point
+  // numbers (Matula, "In-and-Out conversions").
+  // This implies that the algorithm does not produce more than N decimal
+  // digits.
+  //
+  //      N = 17 for p = 53 (IEEE double precision)
+  //      N = 9  for p = 24 (IEEE single precision)
+}
+
+/*!
+v = buf * 10^decimal_exponent
+len is the length of the buffer (number of decimal digits)
+The buffer must be large enough, i.e. >= max_digits10.
+*/
+inline void grisu2_core(char *buf, int &len, int &decimal_exponent, diyfp m_minus,
+                   diyfp v, diyfp m_plus) {
+
+  //  --------(-----------------------+-----------------------)--------    (A)
+  //          m-                      v                       m+
+  //
+  //  --------------------(-----------+-----------------------)--------    (B)
+  //                      m-          v                       m+
+  //
+  // First scale v (and m- and m+) such that the exponent is in the range
+  // [alpha, gamma].
+
+  const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e);
+
+  const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k
+
+  // The exponent of the products is = v.e + c_minus_k.e + q and is in the range
+  // [alpha,gamma]
+  const diyfp w = diyfp::mul(v, c_minus_k);
+  const diyfp w_minus = diyfp::mul(m_minus, c_minus_k);
+  const diyfp w_plus = diyfp::mul(m_plus, c_minus_k);
+
+  //  ----(---+---)---------------(---+---)---------------(---+---)----
+  //          w-                      w                       w+
+  //          = c*m-                  = c*v                   = c*m+
+  //
+  // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and
+  // w+ are now off by a small amount.
+  // In fact:
+  //
+  //      w - v * 10^k < 1 ulp
+  //
+  // To account for this inaccuracy, add resp. subtract 1 ulp.
+  //
+  //  --------+---[---------------(---+---)---------------]---+--------
+  //          w-  M-                  w                   M+  w+
+  //
+  // Now any number in [M-, M+] (bounds included) will round to w when input,
+  // regardless of how the input rounding algorithm breaks ties.
+  //
+  // And digit_gen generates the shortest possible such number in [M-, M+].
+  // Note that this does not mean that Grisu2 always generates the shortest
+  // possible number in the interval (m-, m+).
+  const diyfp M_minus(w_minus.f + 1, w_minus.e);
+  const diyfp M_plus(w_plus.f - 1, w_plus.e);
+
+  decimal_exponent = -cached.k; // = -(-k) = k
+
+  grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus);
+}
+
+/*!
+v = buf * 10^decimal_exponent
+len is the length of the buffer (number of decimal digits)
+The buffer must be large enough, i.e. >= max_digits10.
+*/
+template <typename FloatType>
+void grisu2_wrap(char *buf, int &len, int &decimal_exponent, FloatType value) {
+  static_assert(diyfp::kPrecision >= std::numeric_limits<FloatType>::digits + 3,
+                "internal error: not enough precision");
+
+  // If the neighbors (and boundaries) of 'value' are always computed for
+  // double-precision numbers, all float's can be recovered using strtod (and
+  // strtof). However, the resulting decimal representations are not exactly
+  // "short".
+  //
+  // The documentation for 'std::to_chars'
+  // (https://en.cppreference.com/w/cpp/utility/to_chars) says "value is
+  // converted to a string as if by std::sprintf in the default ("C") locale"
+  // and since sprintf promotes float's to double's, I think this is exactly
+  // what 'std::to_chars' does. On the other hand, the documentation for
+  // 'std::to_chars' requires that "parsing the representation using the
+  // corresponding std::from_chars function recovers value exactly". That
+  // indicates that single precision floating-point numbers should be recovered
+  // using 'std::strtof'.
+  //
+  // NB: If the neighbors are computed for single-precision numbers, there is a
+  // single float
+  //     (7.0385307e-26f) which can't be recovered using strtod. The resulting
+  //     double precision value is off by 1 ulp.
+#if 0
+    const boundaries w = compute_boundaries(static_cast<double>(value));
+#else
+  const boundaries w = compute_boundaries(value);
+#endif
+
+  grisu2_core(buf, len, decimal_exponent, w.minus, w.w, w.plus);
+}
+
+/*!
+@brief appends a decimal representation of e to buf
+@return a pointer to the element following the exponent.
+@pre -1000 < e < 1000
+*/
+inline char *append_exponent(char *buf, int e) {
+
+  if (e < 0) {
+    e = -e;
+    *buf++ = '-';
+  } else {
+    *buf++ = '+';
+  }
+
+  auto k = static_cast<std::uint32_t>(e);
+  if (k < 10) {
+    // Always print at least two digits in the exponent.
+    // This is for compatibility with printf("%g").
+    *buf++ = '0';
+    *buf++ = static_cast<char>('0' + k);
+  } else if (k < 100) {
+    *buf++ = static_cast<char>('0' + k / 10);
+    k %= 10;
+    *buf++ = static_cast<char>('0' + k);
+  } else {
+    *buf++ = static_cast<char>('0' + k / 100);
+    k %= 100;
+    *buf++ = static_cast<char>('0' + k / 10);
+    k %= 10;
+    *buf++ = static_cast<char>('0' + k);
+  }
+
+  return buf;
+}
+
+/*!
+@brief prettify v = buf * 10^decimal_exponent
+If v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point
+notation. Otherwise it will be printed in exponential notation.
+@pre min_exp < 0
+@pre max_exp > 0
+*/
+inline char *format_buffer(char *buf, int len, int decimal_exponent,
+                           int min_exp, int max_exp) {
+
+  const int k = len;
+  const int n = len + decimal_exponent;
+
+  // v = buf * 10^(n-k)
+  // k is the length of the buffer (number of decimal digits)
+  // n is the position of the decimal point relative to the start of the buffer.
+
+  if (k <= n && n <= max_exp) {
+    // digits[000]
+    // len <= max_exp + 2
+
+    std::memset(buf + k, '0', static_cast<size_t>(n) - static_cast<size_t>(k));
+    return buf + (static_cast<size_t>(n));
+  }
+
+  if (0 < n && n <= max_exp) {
+    // dig.its
+    // len <= max_digits10 + 1
+    std::memmove(buf + (static_cast<size_t>(n) + 1), buf + n,
+                 static_cast<size_t>(k) - static_cast<size_t>(n));
+    buf[n] = '.';
+    return buf + (static_cast<size_t>(k) + 1U);
+  }
+
+  if (min_exp < n && n <= 0) {
+    // 0.[000]digits
+    // len <= 2 + (-min_exp - 1) + max_digits10
+
+    std::memmove(buf + (2 + static_cast<size_t>(-n)), buf,
+                 static_cast<size_t>(k));
+    buf[0] = '0';
+    buf[1] = '.';
+    std::memset(buf + 2, '0', static_cast<size_t>(-n));
+    return buf + (2U + static_cast<size_t>(-n) + static_cast<size_t>(k));
+  }
+
+  if (k == 1) {
+    // dE+123
+    // len <= 1 + 5
+
+    buf += 1;
+  } else {
+    // d.igitsE+123
+    // len <= max_digits10 + 1 + 5
+
+    std::memmove(buf + 2, buf + 1, static_cast<size_t>(k) - 1);
+    buf[1] = '.';
+    buf += 1 + static_cast<size_t>(k);
+  }
+
+  *buf++ = 'e';
+  return append_exponent(buf, n - 1);
+}
+
+/*!
+The format of the resulting decimal representation is similar to printf's %g
+format. Returns an iterator pointing past-the-end of the decimal representation.
+@note The input number must be finite, i.e. NaN's and Inf's are not supported.
+@note The buffer must be large enough.
+@note The result is NOT null-terminated.
+*/
+template <typename FloatType>
+char *to_chars(char *first, FloatType value) {
+  bool negative = std::signbit(value);
+  if (negative) {
+    value = -value;
+    *first++ = '-';
+  }
+  if (value == 0) // +-0
+  {
+    *first++ = '0';
+    return first;
+  }
+  // Compute v = buffer * 10^decimal_exponent.
+  // The decimal digits are stored in the buffer, which needs to be interpreted
+  // as an unsigned decimal integer.
+  // len is the length of the buffer, i.e. the number of decimal digits.
+  int len = 0;
+  int decimal_exponent = 0;
+  grisu2_wrap(first, len, decimal_exponent, value);
+  // Format the buffer like printf("%.*g", prec, value)
+  constexpr int kMinExp = -4;
+  constexpr int kMaxExp = std::numeric_limits<double>::digits10;
+
+  return format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);
+}
+} // namespace grisu2

+ 119 - 0
thirdparty/grisu2/patches/0001-godot-changes.patch

@@ -0,0 +1,119 @@
+diff --git a/thirdparty/grisu2/grisu2.h b/thirdparty/grisu2/grisu2.h
+index 19886cce47f..dbc09755fad 100644
+--- a/thirdparty/grisu2/grisu2.h
++++ b/thirdparty/grisu2/grisu2.h
+@@ -1,15 +1,12 @@
+-#ifndef SIMDJSON_SRC_TO_CHARS_CPP
+-#define SIMDJSON_SRC_TO_CHARS_CPP
+-
+-#include <base.h>
++#pragma once
+ 
+ #include <cstring>
+ #include <cstdint>
+ #include <array>
+ #include <cmath>
+ 
+-namespace simdjson {
+-namespace internal {
++namespace grisu2 {
+ /*!
+ implements the Grisu2 algorithm for binary to decimal floating-point
+ conversion.
+@@ -26,7 +23,6 @@ PLDI 2010 [2] Burger, Dybvig, "Printing Floating-Point Numbers Quickly and
+ Accurately", Proceedings of the ACM SIGPLAN 1996 Conference on Programming
+ Language Design and Implementation, PLDI 1996
+ */
+-namespace dtoa_impl {
+ 
+ template <typename Target, typename Source>
+ Target reinterpret_bits(const Source source) {
+@@ -718,7 +714,7 @@ v = buf * 10^decimal_exponent
+ len is the length of the buffer (number of decimal digits)
+ The buffer must be large enough, i.e. >= max_digits10.
+ */
+-inline void grisu2(char *buf, int &len, int &decimal_exponent, diyfp m_minus,
++inline void grisu2_core(char *buf, int &len, int &decimal_exponent, diyfp m_minus,
+                    diyfp v, diyfp m_plus) {
+ 
+   //  --------(-----------------------+-----------------------)--------    (A)
+@@ -775,7 +771,7 @@ len is the length of the buffer (number of decimal digits)
+ The buffer must be large enough, i.e. >= max_digits10.
+ */
+ template <typename FloatType>
+-void grisu2(char *buf, int &len, int &decimal_exponent, FloatType value) {
++void grisu2_wrap(char *buf, int &len, int &decimal_exponent, FloatType value) {
+   static_assert(diyfp::kPrecision >= std::numeric_limits<FloatType>::digits + 3,
+                 "internal error: not enough precision");
+ 
+@@ -804,7 +800,7 @@ void grisu2(char *buf, int &len, int &decimal_exponent, FloatType value) {
+   const boundaries w = compute_boundaries(value);
+ #endif
+ 
+-  grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus);
++  grisu2_core(buf, len, decimal_exponent, w.minus, w.w, w.plus);
+ }
+ 
+ /*!
+@@ -864,10 +860,7 @@ inline char *format_buffer(char *buf, int len, int decimal_exponent,
+     // len <= max_exp + 2
+ 
+     std::memset(buf + k, '0', static_cast<size_t>(n) - static_cast<size_t>(k));
+-    // Make it look like a floating-point number (#362, #378)
+-    buf[n + 0] = '.';
+-    buf[n + 1] = '0';
+-    return buf + (static_cast<size_t>(n)) + 2;
++    return buf + (static_cast<size_t>(n));
+   }
+ 
+   if (0 < n && n <= max_exp) {
+@@ -909,8 +902,6 @@ inline char *format_buffer(char *buf, int len, int decimal_exponent,
+   return append_exponent(buf, n - 1);
+ }
+ 
+-} // namespace dtoa_impl
+-
+ /*!
+ The format of the resulting decimal representation is similar to printf's %g
+ format. Returns an iterator pointing past-the-end of the decimal representation.
+@@ -918,19 +909,15 @@ format. Returns an iterator pointing past-the-end of the decimal representation.
+ @note The buffer must be large enough.
+ @note The result is NOT null-terminated.
+ */
+-char *to_chars(char *first, const char *last, double value) {
+-  static_cast<void>(last); // maybe unused - fix warning
++template <typename FloatType>
++char *to_chars(char *first, FloatType value) {
+   bool negative = std::signbit(value);
+   if (negative) {
+     value = -value;
+     *first++ = '-';
+   }
+-
+   if (value == 0) // +-0
+   {
+-    *first++ = '0';
+-    // Make it look like a floating-point number (#362, #378)
+-    *first++ = '.';
+     *first++ = '0';
+     return first;
+   }
+@@ -940,15 +927,13 @@ char *to_chars(char *first, const char *last, double value) {
+   // len is the length of the buffer, i.e. the number of decimal digits.
+   int len = 0;
+   int decimal_exponent = 0;
+-  dtoa_impl::grisu2(first, len, decimal_exponent, value);
++  grisu2_wrap(first, len, decimal_exponent, value);
+   // Format the buffer like printf("%.*g", prec, value)
+   constexpr int kMinExp = -4;
+   constexpr int kMaxExp = std::numeric_limits<double>::digits10;
+ 
+-  return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp,
+-                                  kMaxExp);
++  return format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);
+ }
+-} // namespace internal
+-} // namespace simdjson
++} // namespace grisu2
+ 
+-#endif // SIMDJSON_SRC_TO_CHARS_CPP