diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt index 4532ab759fdb..becc45f7df1f 100644 --- a/COPYRIGHT.txt +++ b/COPYRIGHT.txt @@ -275,6 +275,11 @@ Comment: Graphite engine Copyright: 2010, SIL International License: Expat +Files: ./thirdparty/grisu2/grisu2.h +Comment: Grisu2 float serialization algorithm +Copyright: 2009 Florian Loitsch and 2018-2023 The simdjson authors +License: Expat and Apache + Files: ./thirdparty/harfbuzz/ Comment: HarfBuzz text shaping library Copyright: 2010-2022, Google, Inc. diff --git a/core/doc_data.cpp b/core/doc_data.cpp index 2881443545f6..13fecdb4bd25 100644 --- a/core/doc_data.cpp +++ b/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("\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("\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 != "inf_neg" && s != "nan") { + if (!s.contains(".") && !s.contains("e")) { + return s + ".0"; + } + } + return s; } else { return p_value.get_construct_string().replace("\n", " "); } diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 61ff176b6a1c..f062e4c3ec09 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -42,6 +42,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 @@ -1849,28 +1851,20 @@ 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); + *last = 0; + return 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); + *last = 0; + return buffer; } String String::md5(const uint8_t *p_md5) { diff --git a/core/string/ustring.h b/core/string/ustring.h index 594e1d362b1d..5d0a966cc1cb 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -450,6 +450,7 @@ class String { 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); diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index d612cb9cead1..c55f04db2606 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1620,6 +1620,16 @@ StringName Variant::get_enum_for_enumeration(Variant::Type p_type, const StringN register_builtin_method(sarray(), m_default_args); #endif +#ifdef DEBUG_METHODS_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(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(sarray(), m_default_args); +#endif + #ifdef DEBUG_METHODS_ENABLED #define bind_methodv(m_type, m_name, m_method, m_arg_names, m_default_args) \ METHOD_CLASS(m_type, m_name, m_method); \ @@ -1798,7 +1808,7 @@ static void _register_variant_builtin_methods_string() { bind_string_method(hex_decode, sarray(), varray()); bind_string_method(to_wchar_buffer, sarray(), varray()); - bind_static_method(String, num_scientific, sarray("number"), varray()); + bind_static_methodv(String, num_scientific, static_cast(&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)); diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp index e9e1c964d309..fb62db47b96c 100644 --- a/core/variant/variant_parser.cpp +++ b/core/variant/variant_parser.cpp @@ -150,6 +150,8 @@ const char *VariantParser::tk_name[TK_MAX] = { static double stor_fix(const String &p_str) { if (p_str == "inf") { return INFINITY; + } else if (p_str == "-inf") { + return -INFINITY; } else if (p_str == "inf_neg") { return -INFINITY; } else if (p_str == "nan") { @@ -700,6 +702,8 @@ Error VariantParser::parse_value(Token &token, Variant &value, Stream *p_stream, value = Variant(); } else if (id == "inf") { value = INFINITY; + } else if (id == "-inf") { + value = -INFINITY; } else if (id == "inf_neg") { value = -INFINITY; } else if (id == "nan") { @@ -1932,20 +1936,20 @@ Error VariantParser::parse(Stream *p_stream, Variant &r_ret, String &r_err_str, ////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////// -static String rtos_fix(double p_value) { +// 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 serialize_real(float p_value) { 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 (isnan(p_value)) { - return "nan"; - } else if (isinf(p_value)) { - if (p_value > 0) { - return "inf"; - } else { - return "inf_neg"; - } - } else { - return rtoss(p_value); + return "0"; // Avoid negative zero (-0) being written, which may annoy git, svn, etc. for changes when they don't exist. + } + return String::num_scientific(p_value); +} + +static String serialize_real(double p_value) { + 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. } + 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) { @@ -1960,8 +1964,8 @@ 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()); - if (s != "inf" && s != "inf_neg" && s != "nan") { + String s = serialize_real(p_variant.operator double()); + if (s != "inf" && s != "-inf" && s != "inf_neg" && s != "nan") { if (!s.contains_char('.') && !s.contains_char('e')) { s += ".0"; } @@ -1977,7 +1981,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str // Math types. case Variant::VECTOR2: { Vector2 v = p_variant; - p_store_string_func(p_store_string_ud, "Vector2(" + rtos_fix(v.x) + ", " + rtos_fix(v.y) + ")"); + p_store_string_func(p_store_string_ud, "Vector2(" + serialize_real(v.x) + ", " + serialize_real(v.y) + ")"); } break; case Variant::VECTOR2I: { Vector2i v = p_variant; @@ -1985,7 +1989,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str } break; case Variant::RECT2: { Rect2 aabb = p_variant; - p_store_string_func(p_store_string_ud, "Rect2(" + rtos_fix(aabb.position.x) + ", " + rtos_fix(aabb.position.y) + ", " + rtos_fix(aabb.size.x) + ", " + rtos_fix(aabb.size.y) + ")"); + p_store_string_func(p_store_string_ud, "Rect2(" + serialize_real(aabb.position.x) + ", " + serialize_real(aabb.position.y) + ", " + serialize_real(aabb.size.x) + ", " + serialize_real(aabb.size.y) + ")"); } break; case Variant::RECT2I: { Rect2i aabb = p_variant; @@ -1993,7 +1997,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str } break; case Variant::VECTOR3: { Vector3 v = p_variant; - p_store_string_func(p_store_string_ud, "Vector3(" + rtos_fix(v.x) + ", " + rtos_fix(v.y) + ", " + rtos_fix(v.z) + ")"); + p_store_string_func(p_store_string_ud, "Vector3(" + serialize_real(v.x) + ", " + serialize_real(v.y) + ", " + serialize_real(v.z) + ")"); } break; case Variant::VECTOR3I: { Vector3i v = p_variant; @@ -2001,7 +2005,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str } break; case Variant::VECTOR4: { Vector4 v = p_variant; - p_store_string_func(p_store_string_ud, "Vector4(" + rtos_fix(v.x) + ", " + rtos_fix(v.y) + ", " + rtos_fix(v.z) + ", " + rtos_fix(v.w) + ")"); + p_store_string_func(p_store_string_ud, "Vector4(" + serialize_real(v.x) + ", " + serialize_real(v.y) + ", " + serialize_real(v.z) + ", " + serialize_real(v.w) + ")"); } break; case Variant::VECTOR4I: { Vector4i v = p_variant; @@ -2009,15 +2013,15 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str } break; case Variant::PLANE: { Plane p = p_variant; - p_store_string_func(p_store_string_ud, "Plane(" + rtos_fix(p.normal.x) + ", " + rtos_fix(p.normal.y) + ", " + rtos_fix(p.normal.z) + ", " + rtos_fix(p.d) + ")"); + p_store_string_func(p_store_string_ud, "Plane(" + serialize_real(p.normal.x) + ", " + serialize_real(p.normal.y) + ", " + serialize_real(p.normal.z) + ", " + serialize_real(p.d) + ")"); } break; case Variant::AABB: { AABB aabb = p_variant; - p_store_string_func(p_store_string_ud, "AABB(" + rtos_fix(aabb.position.x) + ", " + rtos_fix(aabb.position.y) + ", " + rtos_fix(aabb.position.z) + ", " + rtos_fix(aabb.size.x) + ", " + rtos_fix(aabb.size.y) + ", " + rtos_fix(aabb.size.z) + ")"); + p_store_string_func(p_store_string_ud, "AABB(" + serialize_real(aabb.position.x) + ", " + serialize_real(aabb.position.y) + ", " + serialize_real(aabb.position.z) + ", " + serialize_real(aabb.size.x) + ", " + serialize_real(aabb.size.y) + ", " + serialize_real(aabb.size.z) + ")"); } break; case Variant::QUATERNION: { Quaternion quaternion = p_variant; - p_store_string_func(p_store_string_ud, "Quaternion(" + rtos_fix(quaternion.x) + ", " + rtos_fix(quaternion.y) + ", " + rtos_fix(quaternion.z) + ", " + rtos_fix(quaternion.w) + ")"); + p_store_string_func(p_store_string_ud, "Quaternion(" + serialize_real(quaternion.x) + ", " + serialize_real(quaternion.y) + ", " + serialize_real(quaternion.z) + ", " + serialize_real(quaternion.w) + ")"); } break; case Variant::TRANSFORM2D: { String s = "Transform2D("; @@ -2027,7 +2031,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str if (i != 0 || j != 0) { s += ", "; } - s += rtos_fix(m3.columns[i][j]); + s += serialize_real(m3.columns[i][j]); } } @@ -2041,7 +2045,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str if (i != 0 || j != 0) { s += ", "; } - s += rtos_fix(m3.rows[i][j]); + s += serialize_real(m3.rows[i][j]); } } @@ -2056,11 +2060,11 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str if (i != 0 || j != 0) { s += ", "; } - s += rtos_fix(m3.rows[i][j]); + s += serialize_real(m3.rows[i][j]); } } - s = s + ", " + rtos_fix(t.origin.x) + ", " + rtos_fix(t.origin.y) + ", " + rtos_fix(t.origin.z); + s = s + ", " + serialize_real(t.origin.x) + ", " + serialize_real(t.origin.y) + ", " + serialize_real(t.origin.z); p_store_string_func(p_store_string_ud, s + ")"); } break; @@ -2072,7 +2076,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str if (i != 0 || j != 0) { s += ", "; } - s += rtos_fix(t.columns[i][j]); + s += serialize_real(t.columns[i][j]); } } @@ -2082,7 +2086,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str // Misc types. case Variant::COLOR: { Color c = p_variant; - p_store_string_func(p_store_string_ud, "Color(" + rtos_fix(c.r) + ", " + rtos_fix(c.g) + ", " + rtos_fix(c.b) + ", " + rtos_fix(c.a) + ")"); + p_store_string_func(p_store_string_ud, "Color(" + serialize_real(c.r) + ", " + serialize_real(c.g) + ", " + serialize_real(c.b) + ", " + serialize_real(c.a) + ")"); } break; case Variant::STRING_NAME: { String str = p_variant; @@ -2397,7 +2401,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str if (i > 0) { p_store_string_func(p_store_string_ud, ", "); } - p_store_string_func(p_store_string_ud, rtos_fix(ptr[i])); + p_store_string_func(p_store_string_ud, serialize_real(ptr[i])); } p_store_string_func(p_store_string_ud, ")"); @@ -2412,7 +2416,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str if (i > 0) { p_store_string_func(p_store_string_ud, ", "); } - p_store_string_func(p_store_string_ud, rtos_fix(ptr[i])); + p_store_string_func(p_store_string_ud, serialize_real(ptr[i])); } p_store_string_func(p_store_string_ud, ")"); @@ -2442,7 +2446,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str if (i > 0) { p_store_string_func(p_store_string_ud, ", "); } - p_store_string_func(p_store_string_ud, rtos_fix(ptr[i].x) + ", " + rtos_fix(ptr[i].y)); + p_store_string_func(p_store_string_ud, serialize_real(ptr[i].x) + ", " + serialize_real(ptr[i].y)); } p_store_string_func(p_store_string_ud, ")"); @@ -2457,7 +2461,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str if (i > 0) { p_store_string_func(p_store_string_ud, ", "); } - p_store_string_func(p_store_string_ud, rtos_fix(ptr[i].x) + ", " + rtos_fix(ptr[i].y) + ", " + rtos_fix(ptr[i].z)); + p_store_string_func(p_store_string_ud, serialize_real(ptr[i].x) + ", " + serialize_real(ptr[i].y) + ", " + serialize_real(ptr[i].z)); } p_store_string_func(p_store_string_ud, ")"); @@ -2472,7 +2476,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str if (i > 0) { p_store_string_func(p_store_string_ud, ", "); } - p_store_string_func(p_store_string_ud, rtos_fix(ptr[i].r) + ", " + rtos_fix(ptr[i].g) + ", " + rtos_fix(ptr[i].b) + ", " + rtos_fix(ptr[i].a)); + p_store_string_func(p_store_string_ud, serialize_real(ptr[i].r) + ", " + serialize_real(ptr[i].g) + ", " + serialize_real(ptr[i].b) + ", " + serialize_real(ptr[i].a)); } p_store_string_func(p_store_string_ud, ")"); @@ -2487,7 +2491,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str if (i > 0) { p_store_string_func(p_store_string_ud, ", "); } - p_store_string_func(p_store_string_ud, rtos_fix(ptr[i].x) + ", " + rtos_fix(ptr[i].y) + ", " + rtos_fix(ptr[i].z) + ", " + rtos_fix(ptr[i].w)); + p_store_string_func(p_store_string_ud, serialize_real(ptr[i].x) + ", " + serialize_real(ptr[i].y) + ", " + serialize_real(ptr[i].z) + ", " + serialize_real(ptr[i].w)); } p_store_string_func(p_store_string_ud, ")"); diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index 609d7eff3916..99aa34994671 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -689,7 +689,7 @@ 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. - + The animation step value. diff --git a/doc/classes/CharacterBody2D.xml b/doc/classes/CharacterBody2D.xml index 30438be18b79..e2cc1b5d506b 100644 --- a/doc/classes/CharacterBody2D.xml +++ b/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. - + 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. @@ -195,7 +195,7 @@ Current velocity vector in pixels per second, used and modified during calls to [method move_and_slide]. - + Minimum angle (in radians) where the body is allowed to slide when it encounters a slope. The default value equals 15 degrees. This property only affects movement when [member motion_mode] is [constant MOTION_MODE_FLOATING]. diff --git a/doc/classes/CharacterBody3D.xml b/doc/classes/CharacterBody3D.xml index 474adfc6ffe5..9e81a3f9545f 100644 --- a/doc/classes/CharacterBody3D.xml +++ b/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. - + 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. @@ -186,7 +186,7 @@ Current velocity vector (typically meters per second), used and modified during calls to [method move_and_slide]. - + Minimum angle (in radians) where the body is allowed to slide when it encounters a slope. 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]. diff --git a/doc/classes/Color.xml b/doc/classes/Color.xml index 546d90fa3c4f..9e7c5d9f3588 100644 --- a/doc/classes/Color.xml +++ b/doc/classes/Color.xml @@ -518,343 +518,343 @@ - + Alice blue color. - + Antique white color. Aqua color. - + Aquamarine color. - + Azure color. - + Beige color. - + Bisque color. Black color. In GDScript, this is the default value of any color. - + Blanched almond color. Blue color. - + Blue violet color. - + Brown color. - + Burlywood color. - + Cadet blue color. - + Chartreuse color. - + Chocolate color. - + Coral color. - + Cornflower blue color. - + Cornsilk color. - + Crimson color. Cyan color. - + Dark blue color. - + Dark cyan color. - + Dark goldenrod color. - + Dark gray color. - + Dark green color. - + Dark khaki color. - + Dark magenta color. - + Dark olive green color. - + Dark orange color. - + Dark orchid color. - + Dark red color. - + Dark salmon color. - + Dark sea green color. - + Dark slate blue color. - + Dark slate gray color. - + Dark turquoise color. - + Dark violet color. - + Deep pink color. - + Deep sky blue color. - + Dim gray color. - + Dodger blue color. - + Firebrick color. - + Floral white color. - + Forest green color. Fuchsia color. - + Gainsboro color. Ghost white color. - + Gold color. - + Goldenrod color. - + Gray color. Green color. - + Green yellow color. - + Honeydew color. - + Hot pink color. - + Indian red color. - + Indigo color. - + Ivory color. - + Khaki color. - + Lavender color. - + Lavender blush color. - + Lawn green color. - + Lemon chiffon color. - + Light blue color. - + Light coral color. - + Light cyan color. - + Light goldenrod color. Light gray color. - + Light green color. - + Light pink color. - + Light salmon color. - + Light sea green color. - + Light sky blue color. - + Light slate gray color. - + Light steel blue color. - + Light yellow color. Lime color. - + Lime green color. - + Linen color. Magenta color. - + Maroon color. - + Medium aquamarine color. - + Medium blue color. - + Medium orchid color. - + Medium purple color. - + Medium sea green color. - + Medium slate blue color. - + Medium spring green color. - + Medium turquoise color. - + Medium violet red color. - + Midnight blue color. - + Mint cream color. - + Misty rose color. - + Moccasin color. - + Navajo white color. - + Navy blue color. - + Old lace color. - + Olive color. - + Olive drab color. - + Orange color. - + Orange red color. - + Orchid color. - + Pale goldenrod color. - + Pale green color. - + Pale turquoise color. - + Pale violet red color. - + Papaya whip color. - + Peach puff color. - + Peru color. - + Pink color. - + Plum color. - + Powder blue color. - + Purple color. @@ -863,97 +863,97 @@ Red color. - + Rosy brown color. - + Royal blue color. - + Saddle brown color. - + Salmon color. - + Sandy brown color. - + Sea green color. - + Seashell color. - + Sienna color. - + Silver color. - + Sky blue color. - + Slate blue color. - + Slate gray color. - + Snow color. - + Spring green color. - + Steel blue color. - + Tan color. - + Teal color. - + Thistle color. - + Tomato color. Transparent color (white with zero alpha). - + Turquoise color. - + Violet color. - + Web gray color. - + Web green color. - + Web maroon color. - + Web purple color. - + Wheat color. White color. - + White smoke color. Yellow color. - + Yellow green color. diff --git a/doc/classes/ConeTwistJoint3D.xml b/doc/classes/ConeTwistJoint3D.xml index 632b512497b8..d95c9eb1f125 100644 --- a/doc/classes/ConeTwistJoint3D.xml +++ b/doc/classes/ConeTwistJoint3D.xml @@ -36,13 +36,13 @@ The ease with which the joint starts to twist. If it's too low, it takes more force to start twisting the joint. - + 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. - + Twist is the rotation around the twist axis, this value defined how far the joint can twist. Twist is locked if below 0.05. diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml index accdcd0f040d..e5d3b170e123 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -377,10 +377,10 @@ The current zoom value. - + The upper zoom limit. - + The lower zoom limit. diff --git a/doc/classes/HingeJoint3D.xml b/doc/classes/HingeJoint3D.xml index f794853cafa3..72594713e0a6 100644 --- a/doc/classes/HingeJoint3D.xml +++ b/doc/classes/HingeJoint3D.xml @@ -47,7 +47,7 @@ If [code]true[/code], the hinges maximum and minimum rotation, defined by [member angular_limit/lower] and [member angular_limit/upper] has effects. - + The minimum rotation. Only active if [member angular_limit/enable] is [code]true[/code]. @@ -55,7 +55,7 @@ - + The maximum rotation. Only active if [member angular_limit/enable] is [code]true[/code]. diff --git a/doc/classes/Parallax2D.xml b/doc/classes/Parallax2D.xml index cfd282a723a2..728955d92c89 100644 --- a/doc/classes/Parallax2D.xml +++ b/doc/classes/Parallax2D.xml @@ -20,10 +20,10 @@ If [code]true[/code], [Parallax2D]'s position is not affected by the position of the camera. - + 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. - + 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. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index ff5d93516b18..63e3c4089f6b 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2240,7 +2240,7 @@ 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. - + Threshold angular velocity under which a 2D physics body will be considered inactive. See [constant PhysicsServer2D.SPACE_PARAM_BODY_ANGULAR_VELOCITY_SLEEP_THRESHOLD]. @@ -2319,7 +2319,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. - + Threshold angular velocity under which a 3D physics body will be considered inactive. See [constant PhysicsServer3D.SPACE_PARAM_BODY_ANGULAR_VELOCITY_SLEEP_THRESHOLD]. @@ -2367,7 +2367,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. - + 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. @@ -2385,7 +2385,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. - + 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. @@ -2450,7 +2450,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. - + The maximum relative angle by which a body pair can move and still reuse the collision results from the previous physics step, in radians. diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp index 18c75b48c355..84bcaa429aa3 100644 --- a/editor/doc_tools.cpp +++ b/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/marshalls.h" @@ -934,7 +935,7 @@ void DocTools::generate(BitField 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("\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); diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected index f347c95b2c6d..b94e6e956146 100644 --- a/misc/extension_api_validation/4.3-stable.expected +++ b/misc/extension_api_validation/4.3-stable.expected @@ -261,3 +261,141 @@ Validate extension JSON: Error: Field 'classes/PointLight2D/properties/texture': Property hints modified to disallow resource types that don't work. The types allowed are now more restricted, but this change only impacts the editor and not the actual exposed API. No adjustments should be necessary. Decal properties were previously changed from Texture to Texture2D in 4.2, so we need to silence those warnings too. + + +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.5" to "0.20000000298023224". + +Precision of string-serialized Variant constants increased. diff --git a/modules/gltf/doc_classes/GLTFCamera.xml b/modules/gltf/doc_classes/GLTFCamera.xml index 12334683baae..57ddd0bde404 100644 --- a/modules/gltf/doc_classes/GLTFCamera.xml +++ b/modules/gltf/doc_classes/GLTFCamera.xml @@ -46,7 +46,7 @@ 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. - + 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]. diff --git a/modules/gltf/doc_classes/GLTFLight.xml b/modules/gltf/doc_classes/GLTFLight.xml index e07d24a14463..fcb98152fcc6 100644 --- a/modules/gltf/doc_classes/GLTFLight.xml +++ b/modules/gltf/doc_classes/GLTFLight.xml @@ -65,7 +65,7 @@ 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. - + 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. diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml index dd8a11e2b9d2..7e2e6b62fdaf 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerCylinder.xml @@ -12,7 +12,7 @@ The aspect ratio of the slice. Used to set the height relative to the width. - + The central angle of the cylinder. Used to set the width. diff --git a/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml b/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml index 716ea728546c..945eab2a04d0 100644 --- a/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml +++ b/modules/openxr/doc_classes/OpenXRCompositionLayerEquirect.xml @@ -9,19 +9,19 @@ - + The central horizontal angle of the sphere. Used to set the width. The number of segments to use in the fallback mesh. - + The lower vertical angle of the sphere. Used (together with [member upper_vertical_angle]) to set the height. The radius of the sphere. - + The upper vertical angle of the sphere. Used (together with [member lower_vertical_angle]) to set the height. diff --git a/modules/openxr/doc_classes/OpenXRDpadBindingModifier.xml b/modules/openxr/doc_classes/OpenXRDpadBindingModifier.xml index a65bca7997b6..208bd4ed55fd 100644 --- a/modules/openxr/doc_classes/OpenXRDpadBindingModifier.xml +++ b/modules/openxr/doc_classes/OpenXRDpadBindingModifier.xml @@ -36,7 +36,7 @@ When our input value falls below this, our output becomes false. - + The angle of each wedge that identifies the 4 directions of the emulated dpad. diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h index ea60230947d0..329cd98f1f6d 100644 --- a/tests/core/string/test_string.h +++ b/tests/core/string/test_string.h @@ -474,7 +474,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"); @@ -495,6 +494,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(INFINITY) == "inf"); + CHECK(String::num_scientific(-INFINITY) == "-inf"); + CHECK(String::num_scientific(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"); diff --git a/tests/core/variant/test_variant.h b/tests/core/variant/test_variant.h index 599a282b2009..aaedf04f64ad 100644 --- a/tests/core/variant/test_variant.h +++ b/tests/core/variant/test_variant.h @@ -102,7 +102,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."); @@ -116,7 +116,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; @@ -1737,6 +1737,38 @@ TEST_CASE("[Variant] Assignment To Color from Bool,Int,Float,String,Vec2,Vec2i,V CHECK(object_v.get_type() == Variant::COLOR); } +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; diff --git a/thirdparty/README.md b/thirdparty/README.md index 57701e94ee38..691ef0f21a43 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -391,6 +391,17 @@ 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. + + ## harfbuzz - Upstream: https://github.com/harfbuzz/harfbuzz diff --git a/thirdparty/grisu2/LICENSE b/thirdparty/grisu2/LICENSE new file mode 100644 index 000000000000..491c6b1dbea8 --- /dev/null +++ b/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. diff --git a/thirdparty/grisu2/README.md b/thirdparty/grisu2/README.md new file mode 100644 index 000000000000..0cbb8f1ae1ed --- /dev/null +++ b/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. diff --git a/thirdparty/grisu2/grisu2.h b/thirdparty/grisu2/grisu2.h new file mode 100644 index 000000000000..dbc09755fadd --- /dev/null +++ b/thirdparty/grisu2/grisu2.h @@ -0,0 +1,939 @@ +#ifndef SIMDJSON_GRISU2_H +#define SIMDJSON_GRISU2_H + +#include +#include +#include +#include + +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 +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 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::is_iec559, + "internal error: dtoa_short requires an IEEE-754 " + "floating-point implementation"); + + constexpr int kPrecision = + std::numeric_limits::digits; // = p (includes the hidden bit) + constexpr int kBias = + std::numeric_limits::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::type; + + const std::uint64_t bits = reinterpret_bits(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(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 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(f > 0); + + const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / + kCachedPowersDecStep; + + const cached_power cached = kCachedPowers[static_cast(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( + 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('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('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 +void grisu2_wrap(char *buf, int &len, int &decimal_exponent, FloatType value) { + static_assert(diyfp::kPrecision >= std::numeric_limits::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(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(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('0' + k); + } else if (k < 100) { + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('0' + k); + } else { + *buf++ = static_cast('0' + k / 100); + k %= 100; + *buf++ = static_cast('0' + k / 10); + k %= 10; + *buf++ = static_cast('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(n) - static_cast(k)); + return buf + (static_cast(n)); + } + + if (0 < n && n <= max_exp) { + // dig.its + // len <= max_digits10 + 1 + std::memmove(buf + (static_cast(n) + 1), buf + n, + static_cast(k) - static_cast(n)); + buf[n] = '.'; + return buf + (static_cast(k) + 1U); + } + + if (min_exp < n && n <= 0) { + // 0.[000]digits + // len <= 2 + (-min_exp - 1) + max_digits10 + + std::memmove(buf + (2 + static_cast(-n)), buf, + static_cast(k)); + buf[0] = '0'; + buf[1] = '.'; + std::memset(buf + 2, '0', static_cast(-n)); + return buf + (2U + static_cast(-n) + static_cast(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(k) - 1); + buf[1] = '.'; + buf += 1 + static_cast(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 +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::digits10; + + return format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp); +} +} // namespace grisu2 + +#endif // SIMDJSON_GRISU2_H diff --git a/thirdparty/grisu2/patches/godot.patch b/thirdparty/grisu2/patches/godot.patch new file mode 100644 index 000000000000..4ed562db8aa2 --- /dev/null +++ b/thirdparty/grisu2/patches/godot.patch @@ -0,0 +1,121 @@ +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 ++#ifndef SIMDJSON_GRISU2_H ++#define SIMDJSON_GRISU2_H + + #include + #include + #include + #include + +-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 + 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 +-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::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(n) - static_cast(k)); +- // Make it look like a floating-point number (#362, #378) +- buf[n + 0] = '.'; +- buf[n + 1] = '0'; +- return buf + (static_cast(n)) + 2; ++ return buf + (static_cast(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(last); // maybe unused - fix warning ++template ++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::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 ++#endif // SIMDJSON_GRISU2_H