diff --git a/AK/Format.cpp b/AK/Format.cpp index 9206428204..6f100e7f8d 100644 --- a/AK/Format.cpp +++ b/AK/Format.cpp @@ -447,6 +447,29 @@ ErrorOr FormatBuilder::put_fixed_point( } #ifndef KERNEL +static ErrorOr round_up_digits(StringBuilder& digits_builder) +{ + auto digits_buffer = TRY(digits_builder.to_byte_buffer()); + int current_position = digits_buffer.size() - 1; + + while (current_position >= 0) { + if (digits_buffer[current_position] == '.') { + --current_position; + continue; + } + ++digits_buffer[current_position]; + if (digits_buffer[current_position] <= '9') + break; + digits_buffer[current_position] = '0'; + --current_position; + } + + digits_builder.clear(); + if (current_position < 0) + TRY(digits_builder.try_append('1')); + return digits_builder.try_append(digits_buffer); +} + ErrorOr FormatBuilder::put_f64( double value, u8 base, @@ -484,37 +507,40 @@ ErrorOr FormatBuilder::put_f64( value = -value; TRY(format_builder.put_u64(static_cast(value), base, false, upper_case, false, use_separator, Align::Right, 0, ' ', sign_mode, is_negative)); + value -= static_cast(value); if (precision > 0) { // FIXME: This is a terrible approximation but doing it properly would be a lot of work. If someone is up for that, a good // place to start would be the following video from CppCon 2019: // https://youtu.be/4P_kbF0EbZM (Stephan T. Lavavej “Floating-Point : Making Your Code 10x Faster With C++17's Final Boss”) - value -= static_cast(value); - double epsilon = 0.5; - for (size_t i = 0; i < precision; ++i) - epsilon /= 10.0; - - size_t visible_precision = 0; - for (; visible_precision < precision; ++visible_precision) { - if (value - static_cast(value) < epsilon && display_mode != RealNumberDisplayMode::FixedPoint) - break; - value *= 10.0; - epsilon *= 10.0; + if (!zero_pad && display_mode != RealNumberDisplayMode::FixedPoint) { + for (size_t i = 0; i < precision; ++i) + epsilon /= 10.0; } - if (zero_pad || visible_precision > 0) - TRY(string_builder.try_append('.')); + for (size_t digit = 0; digit < precision; ++digit) { + if (!zero_pad && display_mode != RealNumberDisplayMode::FixedPoint && value - static_cast(value) < epsilon) + break; - if (visible_precision > 0) - TRY(format_builder.put_u64(static_cast(value), base, false, upper_case, true, false, Align::Right, visible_precision)); + value *= 10.0; + epsilon *= 10.0; - if (zero_pad && (precision - visible_precision) > 0) - TRY(format_builder.put_u64(0, base, false, false, true, false, Align::Right, precision - visible_precision)); + if (value > NumericLimits::max()) + value -= static_cast(value) - (static_cast(value) % 10); + + if (digit == 0) + TRY(string_builder.try_append('.')); + + TRY(string_builder.try_append('0' + (static_cast(value) % 10))); + } } - TRY(put_string(string_builder.string_view(), align, min_width, NumericLimits::max(), fill)); - return {}; + // Round up if the following decimal is 5 or higher + if (static_cast(value * 10.0) % 10 >= 5) + TRY(round_up_digits(string_builder)); + + return put_string(string_builder.string_view(), align, min_width, NumericLimits::max(), fill); } ErrorOr FormatBuilder::put_f80( @@ -553,31 +579,39 @@ ErrorOr FormatBuilder::put_f80( value = -value; TRY(format_builder.put_u64(static_cast(value), base, false, upper_case, false, use_separator, Align::Right, 0, ' ', sign_mode, is_negative)); + value -= static_cast(value); if (precision > 0) { // FIXME: This is a terrible approximation but doing it properly would be a lot of work. If someone is up for that, a good // place to start would be the following video from CppCon 2019: // https://youtu.be/4P_kbF0EbZM (Stephan T. Lavavej “Floating-Point : Making Your Code 10x Faster With C++17's Final Boss”) - value -= static_cast(value); - long double epsilon = 0.5l; - for (size_t i = 0; i < precision; ++i) - epsilon /= 10.0l; + if (display_mode != RealNumberDisplayMode::FixedPoint) { + for (size_t i = 0; i < precision; ++i) + epsilon /= 10.0l; + } - size_t visible_precision = 0; - for (; visible_precision < precision; ++visible_precision) { - if (value - static_cast(value) < epsilon && display_mode != RealNumberDisplayMode::FixedPoint) + for (size_t digit = 0; digit < precision; ++digit) { + if (display_mode != RealNumberDisplayMode::FixedPoint && value - static_cast(value) < epsilon) break; + value *= 10.0l; epsilon *= 10.0l; - } - if (visible_precision > 0) { - string_builder.append('.'); - TRY(format_builder.put_u64(static_cast(value), base, false, upper_case, true, false, Align::Right, visible_precision)); + if (value > NumericLimits::max()) + value -= static_cast(value) - (static_cast(value) % 10); + + if (digit == 0) + TRY(string_builder.try_append('.')); + + TRY(string_builder.try_append('0' + (static_cast(value) % 10))); } } + // Round up if the following decimal is 5 or higher + if (static_cast(value * 10.0l) % 10 >= 5) + TRY(round_up_digits(string_builder)); + TRY(put_string(string_builder.string_view(), align, min_width, NumericLimits::max(), fill)); return {}; } diff --git a/Tests/AK/TestFormat.cpp b/Tests/AK/TestFormat.cpp index 4602f342ad..823555f37d 100644 --- a/Tests/AK/TestFormat.cpp +++ b/Tests/AK/TestFormat.cpp @@ -262,6 +262,18 @@ TEST_CASE(floating_point_numbers) EXPECT_EQ(DeprecatedString::formatted("{:'.4}", 1234.5678), "1,234.5678"); EXPECT_EQ(DeprecatedString::formatted("{:'.4}", -1234.5678), "-1,234.5678"); + EXPECT_EQ(DeprecatedString::formatted("{:.30f}", 1.0), "1.000000000000000000000000000000"); + EXPECT_EQ(DeprecatedString::formatted("{:.30f}", 1.5), "1.500000000000000000000000000000"); + EXPECT_EQ(DeprecatedString::formatted("{:.30f}", -2.0), "-2.000000000000000000000000000000"); + + EXPECT_EQ(DeprecatedString::formatted("{:.0f}", 1.4), "1"); + EXPECT_EQ(DeprecatedString::formatted("{:.0f}", 1.5), "2"); + EXPECT_EQ(DeprecatedString::formatted("{:.0f}", -1.9), "-2"); + + EXPECT_EQ(DeprecatedString::formatted("{:.1f}", 1.4), "1.4"); + EXPECT_EQ(DeprecatedString::formatted("{:.1f}", 1.99), "2.0"); + EXPECT_EQ(DeprecatedString::formatted("{:.1f}", 9.999), "10.0"); + EXPECT_EQ(DeprecatedString::formatted("{}", NAN), "nan"); EXPECT_EQ(DeprecatedString::formatted("{}", INFINITY), "inf"); EXPECT_EQ(DeprecatedString::formatted("{}", -INFINITY), "-inf"); @@ -276,11 +288,6 @@ TEST_CASE(no_precision_no_trailing_number) EXPECT_EQ(DeprecatedString::formatted("{:.0}", 0.1), "0"); } -TEST_CASE(yay_this_implementation_sucks) -{ - EXPECT_EQ(DeprecatedString::formatted("{:.0}", .99999999999), "0"); -} - TEST_CASE(precision_with_trailing_zeros) { EXPECT_EQ(DeprecatedString::formatted("{:0.3}", 1.12), "1.120"); diff --git a/Tests/LibWeb/Text/expected/css/getComputedStyle-transform.txt b/Tests/LibWeb/Text/expected/css/getComputedStyle-transform.txt index d520b6ca2d..ad943ead8b 100644 --- a/Tests/LibWeb/Text/expected/css/getComputedStyle-transform.txt +++ b/Tests/LibWeb/Text/expected/css/getComputedStyle-transform.txt @@ -8,10 +8,10 @@ translateY(1%) => matrix(1, 0, 0, 1, 0, 0) scale(1, 2) => matrix(1, 0, 0, 2, 0, 0) scaleX(2) => matrix(2, 0, 0, 1, 0, 0) scaleY(2.5) => matrix(1, 0, 0, 2.5, 0, 0) -rotate(1deg) => matrix(0.999847, 0.017452, -0.017452, 0.999847, 0, 0) -rotateX(1rad) => matrix3d(1, 0, 0, 0, 0, 0.540302, 0.841470, 0, 0, -0.841470, 0.540302, 0, 0, 0, 0, 1) -rotateY(1grad) => matrix3d(0.999876, 0, -0.015707, 0, 0, 1, 0, 0, 0.015707, 0, 0.999876, 0, 0, 0, 0, 1) +rotate(1deg) => matrix(0.999848, 0.017452, -0.017452, 0.999848, 0, 0) +rotateX(1rad) => matrix3d(1, 0, 0, 0, 0, 0.540302, 0.841471, 0, 0, -0.841471, 0.540302, 0, 0, 0, 0, 1) +rotateY(1grad) => matrix3d(0.999877, 0, -0.015707, 0, 0, 1, 0, 0, 0.015707, 0, 0.999877, 0, 0, 0, 0, 1) rotateZ(1turn) => matrix(1, 0, -0, 1, 0, 0) -skew(1deg, 1rad) => matrix(1, 1.557407, 0.017455, 1, 0, 0) +skew(1deg, 1rad) => matrix(1, 1.557408, 0.017455, 1, 0, 0) skewX(1deg) => matrix(1, 0, 0.017455, 1, 0, 0) -skewY(1rad) => matrix(1, 1.557407, 0, 1, 0, 0) +skewY(1rad) => matrix(1, 1.557408, 0, 1, 0, 0)