mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 15:12:45 +00:00 
			
		
		
		
	LibJS: Implement (mostly) spec compliant version of Number.toString()
This commit is contained in:
		
							parent
							
								
									be9c2feff0
								
							
						
					
					
						commit
						d3524f47a0
					
				
					 4 changed files with 151 additions and 20 deletions
				
			
		|  | @ -364,6 +364,15 @@ int String::replace(const String& needle, const String& replacement, bool all_oc | |||
|     return positions.size(); | ||||
| } | ||||
| 
 | ||||
| String String::reverse() const | ||||
| { | ||||
|     StringBuilder reversed_string; | ||||
|     for (size_t i = length(); i-- > 0;) { | ||||
|         reversed_string.append(characters()[i]); | ||||
|     } | ||||
|     return reversed_string.to_string(); | ||||
| } | ||||
| 
 | ||||
| String escape_html_entities(const StringView& html) | ||||
| { | ||||
|     StringBuilder builder; | ||||
|  |  | |||
|  | @ -258,6 +258,7 @@ public: | |||
|     StringView view() const; | ||||
| 
 | ||||
|     int replace(const String& needle, const String& replacement, bool all_occurrences = false); | ||||
|     String reverse() const; | ||||
| 
 | ||||
|     template<typename T, typename... Rest> | ||||
|     bool is_one_of(const T& string, Rest... rest) const | ||||
|  |  | |||
|  | @ -80,6 +80,136 @@ ALWAYS_INLINE bool both_bigint(const Value& lhs, const Value& rhs) | |||
|     return lhs.is_bigint() && rhs.is_bigint(); | ||||
| } | ||||
| 
 | ||||
| static String double_to_string(double d) | ||||
| { | ||||
|     // https://tc39.es/ecma262/#sec-numeric-types-number-tostring
 | ||||
|     if (isnan(d)) | ||||
|         return "NaN"; | ||||
|     if (d == +0.0 || d == -0.0) | ||||
|         return "0"; | ||||
|     if (d < +0.0) { | ||||
|         StringBuilder builder; | ||||
|         builder.append('-'); | ||||
|         builder.append(double_to_string(-d)); | ||||
|         return builder.to_string(); | ||||
|     } | ||||
|     if (d == INFINITY) | ||||
|         return "Infinity"; | ||||
| 
 | ||||
|     StringBuilder number_string_builder; | ||||
| 
 | ||||
|     size_t start_index = 0; | ||||
|     size_t end_index = 0; | ||||
|     size_t intpart_end = 0; | ||||
| 
 | ||||
|     // generate integer part (reversed)
 | ||||
|     double intPart; | ||||
|     double frac_part; | ||||
|     frac_part = modf(d, &intPart); | ||||
|     while (intPart > 0) { | ||||
|         number_string_builder.append('0' + (int)fmod(intPart, 10)); | ||||
|         end_index++; | ||||
|         intPart = floor(intPart / 10); | ||||
|     } | ||||
| 
 | ||||
|     auto reversed_integer_part = number_string_builder.to_string().reverse(); | ||||
|     number_string_builder.clear(); | ||||
|     number_string_builder.append(reversed_integer_part); | ||||
| 
 | ||||
|     intpart_end = end_index; | ||||
| 
 | ||||
|     int exponent = 0; | ||||
| 
 | ||||
|     // generate fractional part
 | ||||
|     while (frac_part > 0) { | ||||
|         double old_frac_part = frac_part; | ||||
|         frac_part *= 10; | ||||
|         frac_part = modf(frac_part, &intPart); | ||||
|         if (old_frac_part == frac_part) | ||||
|             break; | ||||
|         number_string_builder.append('0' + (int)intPart); | ||||
|         end_index++; | ||||
|         exponent--; | ||||
|     } | ||||
| 
 | ||||
|     auto number_string = number_string_builder.to_string(); | ||||
| 
 | ||||
|     // HACK: (sunverwerth) I'm not sure how the ECMAScript spec deals with numbers that
 | ||||
|     // can not be exactly represented in IEE754 so I'm cutting off after the 15th fractional digit.
 | ||||
|     // Otherwise 3.14.toString() would come out as "3.140000000000000124344978758017532527446746826171875"
 | ||||
|     // Chrome and Firefox output the expected "3.14" here
 | ||||
|     if (end_index > intpart_end + 15) { | ||||
|         exponent += end_index - intpart_end - 15; | ||||
|         end_index = intpart_end + 15; | ||||
|     } | ||||
|     // HACK end
 | ||||
| 
 | ||||
|     // remove leading zeroes
 | ||||
|     while (start_index < end_index && number_string[start_index] == '0') { | ||||
|         start_index++; | ||||
|     } | ||||
| 
 | ||||
|     // remove trailing zeroes
 | ||||
|     while (end_index > 0 && number_string[end_index - 1] == '0') { | ||||
|         end_index--; | ||||
|         exponent++; | ||||
|     } | ||||
| 
 | ||||
|     if (end_index <= start_index) | ||||
|         return "0"; | ||||
| 
 | ||||
|     auto digits = number_string.substring_view(start_index, end_index - start_index); | ||||
| 
 | ||||
|     int number_of_digits = end_index - start_index; | ||||
| 
 | ||||
|     exponent += number_of_digits; | ||||
| 
 | ||||
|     StringBuilder builder; | ||||
| 
 | ||||
|     if (number_of_digits <= exponent && exponent <= 21) { | ||||
|         builder.append(digits); | ||||
|         builder.append(String::repeated('0', exponent - number_of_digits)); | ||||
|         return builder.to_string(); | ||||
|     } | ||||
|     if (0 < exponent && exponent <= 21) { | ||||
|         builder.append(digits.substring_view(0, exponent)); | ||||
|         builder.append('.'); | ||||
|         builder.append(digits.substring_view(exponent)); | ||||
|         return builder.to_string(); | ||||
|     } | ||||
|     if (-6 < exponent && exponent <= 0) { | ||||
|         builder.append("0."); | ||||
|         builder.append(String::repeated('0', -exponent)); | ||||
|         builder.append(digits); | ||||
|         return builder.to_string(); | ||||
|     } | ||||
|     if (number_of_digits == 1) { | ||||
|         builder.append(digits); | ||||
|         builder.append('e'); | ||||
| 
 | ||||
|         if (exponent - 1 > 0) | ||||
|             builder.append('+'); | ||||
|         else | ||||
|             builder.append('-'); | ||||
| 
 | ||||
|         builder.append(String::format("%d", abs(exponent - 1))); | ||||
|         return builder.to_string(); | ||||
|     } | ||||
| 
 | ||||
|     builder.append(digits[0]); | ||||
|     builder.append('.'); | ||||
|     builder.append(digits.substring_view(1)); | ||||
|     builder.append('e'); | ||||
| 
 | ||||
|     if (exponent - 1 > 0) | ||||
|         builder.append('+'); | ||||
|     else | ||||
|         builder.append('-'); | ||||
| 
 | ||||
|     builder.append(String::format("%d", abs(exponent - 1))); | ||||
|     return builder.to_string(); | ||||
| } | ||||
| 
 | ||||
| bool Value::is_array() const | ||||
| { | ||||
|     return is_object() && as_object().is_array(); | ||||
|  | @ -128,14 +258,7 @@ String Value::to_string_without_side_effects() const | |||
|     case Type::Boolean: | ||||
|         return m_value.as_bool ? "true" : "false"; | ||||
|     case Type::Number: | ||||
|         if (is_nan()) | ||||
|             return "NaN"; | ||||
|         if (is_infinity()) | ||||
|             return is_negative_infinity() ? "-Infinity" : "Infinity"; | ||||
|         if (is_integer()) | ||||
|             return String::number(as_i32()); | ||||
|         // FIXME: This should be more sophisticated: don't cut off decimals, don't include trailing zeros
 | ||||
|         return String::formatted("{:.4}", m_value.as_double); | ||||
|         return double_to_string(m_value.as_double); | ||||
|     case Type::String: | ||||
|         return m_value.as_string->string(); | ||||
|     case Type::Symbol: | ||||
|  | @ -173,14 +296,7 @@ String Value::to_string(GlobalObject& global_object, bool legacy_null_to_empty_s | |||
|     case Type::Boolean: | ||||
|         return m_value.as_bool ? "true" : "false"; | ||||
|     case Type::Number: | ||||
|         if (is_nan()) | ||||
|             return "NaN"; | ||||
|         if (is_infinity()) | ||||
|             return is_negative_infinity() ? "-Infinity" : "Infinity"; | ||||
|         if (is_integer()) | ||||
|             return String::number(as_i32()); | ||||
|         // FIXME: This should be more sophisticated: don't cut off decimals, don't include trailing zeros
 | ||||
|         return String::formatted("{:.4}", m_value.as_double); | ||||
|         return double_to_string(m_value.as_double); | ||||
|     case Type::String: | ||||
|         return m_value.as_string->string(); | ||||
|     case Type::Symbol: | ||||
|  |  | |||
|  | @ -40,10 +40,15 @@ describe("correct behavior", () => { | |||
|     }); | ||||
| 
 | ||||
|     test("numeric keys", () => { | ||||
|         expect({0x10:true}).toBe({16:true}); | ||||
|         expect({0b10:true}).toBe({2:true}); | ||||
|         expect({0o10:true}).toBe({8:true}); | ||||
|         expect({.5:true}).toBe({"0.5":true}); | ||||
|         const hex = {0x10: "16"}; | ||||
|         const oct = {0o10: "8"}; | ||||
|         const bin = {0b10: "2"}; | ||||
|         const float = {.5: "0.5"}; | ||||
|          | ||||
|         expect(hex["16"]).toBe("16"); | ||||
|         expect(oct["8"]).toBe("8"); | ||||
|         expect(bin["2"]).toBe("2"); | ||||
|         expect(float["0.5"]).toBe("0.5"); | ||||
|     }); | ||||
| 
 | ||||
|     test("computed properties", () => { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Stephan Unverwerth
						Stephan Unverwerth