mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 09:48:11 +00:00
AK: Add framework for a unified floating point to string conversion
Currently, the floating point to string conversion is implemented several times across the codebase. This commit provides a pretty low-level function to unify all of such conversions. It converts the given double to a fixed point decimal satisfying a few correctness criteria.
This commit is contained in:
parent
17c9a3e8d9
commit
fdc53a5995
5 changed files with 1243 additions and 0 deletions
|
@ -16,6 +16,7 @@ set(AK_SOURCES
|
||||||
StackInfo.cpp
|
StackInfo.cpp
|
||||||
String.cpp
|
String.cpp
|
||||||
StringBuilder.cpp
|
StringBuilder.cpp
|
||||||
|
StringFloatingPointConversions.cpp
|
||||||
StringImpl.cpp
|
StringImpl.cpp
|
||||||
StringUtils.cpp
|
StringUtils.cpp
|
||||||
StringView.cpp
|
StringView.cpp
|
||||||
|
|
1124
AK/StringFloatingPointConversions.cpp
Normal file
1124
AK/StringFloatingPointConversions.cpp
Normal file
File diff suppressed because it is too large
Load diff
43
AK/StringFloatingPointConversions.h
Normal file
43
AK/StringFloatingPointConversions.h
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022, Dan Klishch <danilklishch@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef KERNEL
|
||||||
|
# error This file should not be included in the KERNEL, as doubles should not appear in the \
|
||||||
|
kernel code, although the conversion currently does not use any floating point \
|
||||||
|
computations.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <AK/Concepts.h>
|
||||||
|
#include <AK/Types.h>
|
||||||
|
|
||||||
|
namespace AK {
|
||||||
|
|
||||||
|
struct FloatingPointExponentialForm {
|
||||||
|
bool sign;
|
||||||
|
u64 fraction;
|
||||||
|
i32 exponent;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This function finds the representation of `value' in the form of
|
||||||
|
/// `(-1) ^ sign * fraction * 10 ^ exponent', such that (applying in the order of enumeration):
|
||||||
|
///
|
||||||
|
/// 1. sign is either 0 or 1, fraction is a non-negative number, exponent is an integer.
|
||||||
|
/// 2. For +0, it is {.sign = 0, .fraction = 0, .exponent = 0},
|
||||||
|
/// for -0, is {.sign = 1, .fraction = 0, .exponent = 0},
|
||||||
|
/// for +inf, -inf, and NaN is undefined.
|
||||||
|
/// 3. `(-1) ^ sign * fraction * 10 ^ exponent', computed with an infinite precision, rounds to
|
||||||
|
/// `value' in the half to even mode.
|
||||||
|
/// 4. len(str(fraction)) is minimal.
|
||||||
|
/// 5. `abs((-1) ^ sign * fraction * 10 ^ exponent - value)' is minimal.
|
||||||
|
/// 6. fraction is even.
|
||||||
|
template<FloatingPoint T>
|
||||||
|
FloatingPointExponentialForm convert_floating_point_to_decimal_exponential_form(T value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
using AK::convert_floating_point_to_decimal_exponential_form;
|
|
@ -62,6 +62,7 @@ set(AK_TEST_SOURCES
|
||||||
TestStack.cpp
|
TestStack.cpp
|
||||||
TestStdLibExtras.cpp
|
TestStdLibExtras.cpp
|
||||||
TestString.cpp
|
TestString.cpp
|
||||||
|
TestStringFloatingPointConversions.cpp
|
||||||
TestStringUtils.cpp
|
TestStringUtils.cpp
|
||||||
TestStringView.cpp
|
TestStringView.cpp
|
||||||
TestTime.cpp
|
TestTime.cpp
|
||||||
|
|
74
Tests/AK/TestStringFloatingPointConversions.cpp
Normal file
74
Tests/AK/TestStringFloatingPointConversions.cpp
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022, Dan Klishch <danilklishch@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/BitCast.h>
|
||||||
|
#include <AK/StringFloatingPointConversions.h>
|
||||||
|
#include <LibTest/TestCase.h>
|
||||||
|
|
||||||
|
static bool operator!=(AK::FloatingPointExponentialForm a, AK::FloatingPointExponentialForm b)
|
||||||
|
{
|
||||||
|
return a.sign != b.sign || a.exponent != b.exponent || a.fraction != b.fraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct AK::Formatter<AK::FloatingPointExponentialForm> : Formatter<FormatString> {
|
||||||
|
ErrorOr<void> format(FormatBuilder& builder, AK::FloatingPointExponentialForm value)
|
||||||
|
{
|
||||||
|
return Formatter<FormatString>::format(builder, "(s={} f={} e={})"sv, value.sign, value.fraction, value.exponent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DOES_CONVERT_DOUBLE_TO(value, sign, fraction, exponent) \
|
||||||
|
do { \
|
||||||
|
EXPECT_EQ( \
|
||||||
|
convert_floating_point_to_decimal_exponential_form(static_cast<double>(value)), \
|
||||||
|
(AK::FloatingPointExponentialForm { sign, fraction, exponent })); \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
// Tests here only check basic cases. While writing, I mostly relied on the benchmarks and
|
||||||
|
// stress tests, which can be found at https://github.com/DanShaders/serenity-arithmetic-benchmark/blob/master/StringFloatingPointConversions.cpp .
|
||||||
|
|
||||||
|
TEST_CASE(double_conversion)
|
||||||
|
{
|
||||||
|
DOES_CONVERT_DOUBLE_TO(0, 0, 0, 0);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(-0., 1, 0, 0);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(1, 0, 1, 0);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(-1, 1, 1, 0);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(.1, 0, 1, -1);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(.2, 0, 2, -1);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(.3, 0, 3, -1);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(.12345, 0, 12345, -5);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(.0012345, 0, 12345, -7);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(.1 + .2, 0, 30000000000000004, -17);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(17976931348623157e292, 0, 17976931348623157, 292);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(-17976931348623157e292, 1, 17976931348623157, 292);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(22250738585072014e-324, 0, 22250738585072014, -324);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(-22250738585072014e-324, 1, 22250738585072014, -324);
|
||||||
|
DOES_CONVERT_DOUBLE_TO(bit_cast<double>(0xc3c04222300db8acULL), 1, 23430728857074627, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DOES_CONVERT_FLOAT_TO(value, sign, fraction, exponent) \
|
||||||
|
do { \
|
||||||
|
EXPECT_EQ( \
|
||||||
|
convert_floating_point_to_decimal_exponential_form(static_cast<float>(value)), \
|
||||||
|
(AK::FloatingPointExponentialForm { sign, fraction, exponent })); \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
TEST_CASE(float_conversion)
|
||||||
|
{
|
||||||
|
DOES_CONVERT_FLOAT_TO(0, 0, 0, 0);
|
||||||
|
DOES_CONVERT_FLOAT_TO(-0., 1, 0, 0);
|
||||||
|
DOES_CONVERT_FLOAT_TO(1, 0, 1, 0);
|
||||||
|
DOES_CONVERT_FLOAT_TO(-1, 1, 1, 0);
|
||||||
|
DOES_CONVERT_FLOAT_TO(.1, 0, 1, -1);
|
||||||
|
DOES_CONVERT_FLOAT_TO(.2, 0, 2, -1);
|
||||||
|
DOES_CONVERT_FLOAT_TO(.3, 0, 3, -1);
|
||||||
|
DOES_CONVERT_FLOAT_TO(0.025, 0, 25, -3);
|
||||||
|
DOES_CONVERT_FLOAT_TO(34028235e31, 0, 34028235, 31);
|
||||||
|
DOES_CONVERT_FLOAT_TO(-34028235e31, 1, 34028235, 31);
|
||||||
|
DOES_CONVERT_FLOAT_TO(11754944e-45, 0, 11754944, -45);
|
||||||
|
DOES_CONVERT_FLOAT_TO(-11754944e-45, 1, 11754944, -45);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue