mirror of
https://github.com/tfussell/xlnt.git
synced 2024-03-22 13:11:17 +08:00
Merge branch 'Issue#318_fp-equality' of https://github.com/Crzyrndm/xlnt into Crzyrndm-Issue#318_fp-equality
This commit is contained in:
commit
90b672cf6b
|
@ -23,8 +23,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <xlnt/xlnt_config.hpp>
|
||||
#include <xlnt/utils/exceptions.hpp>
|
||||
#include "xlnt/xlnt_config.hpp"
|
||||
#include "xlnt/utils/exceptions.hpp"
|
||||
#include "../source/detail/numeric_utils.hpp"
|
||||
#include <type_traits>
|
||||
|
||||
namespace xlnt {
|
||||
|
@ -49,6 +50,23 @@ class optional
|
|||
using set_move_noexcept_t = typename std::conditional<std::is_nothrow_move_constructible<T>{} && std::is_nothrow_move_assignable<T>{}, std::true_type, std::false_type>::type;
|
||||
using clear_noexcept_t = typename std::conditional<std::is_nothrow_destructible<T>{}, std::true_type, std::false_type>::type;
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Default equality operation, just uses operator==
|
||||
/// </summary>
|
||||
template <typename U = T, typename std::enable_if<!std::is_floating_point<U>::value>::type * = nullptr>
|
||||
constexpr bool compare_equal(const U &lhs, const U &rhs) const
|
||||
{
|
||||
return lhs == rhs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// equality operation for floating point numbers. Provides "fuzzy" equality
|
||||
/// </summary>
|
||||
template <typename U = T, typename std::enable_if<std::is_floating_point<U>::value>::type * = nullptr>
|
||||
constexpr bool compare_equal(const U &lhs, const U &rhs) const
|
||||
{
|
||||
return detail::float_equals(lhs, rhs);
|
||||
}
|
||||
|
||||
public:
|
||||
/// <summary>
|
||||
|
@ -281,7 +299,8 @@ public:
|
|||
{
|
||||
return true;
|
||||
}
|
||||
return value_ref() == other.value_ref();
|
||||
// equality is overloaded to provide fuzzy equality when T is a fp number
|
||||
return compare_equal(value_ref(), other.value_ref());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright (c) 2014-2018 Thomas Fussell
|
||||
//
|
||||
// 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, WRISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE
|
||||
//
|
||||
// @license: http://www.opensource.org/licenses/mit-license.php
|
||||
// @author: see AUTHORS file
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <xlnt/xlnt_config.hpp>
|
||||
#include <sstream>
|
||||
|
||||
namespace xlnt {
|
||||
/// <summary>
|
||||
/// Takes in any nuber and outputs a string form of that number which will
|
||||
/// serialise and deserialise without loss of precision
|
||||
/// </summary>
|
||||
template <typename Number>
|
||||
std::string serialize_number_to_string(Number num)
|
||||
{
|
||||
// more digits and excel won't match
|
||||
constexpr int Excel_Digit_Precision = 15; //sf
|
||||
std::stringstream ss;
|
||||
ss.precision(Excel_Digit_Precision);
|
||||
ss << num;
|
||||
return ss.str();
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#include <xlnt/xlnt_config.hpp>
|
||||
#include <xlnt/utils/optional.hpp>
|
||||
#include "../source/detail/numeric_utils.hpp"
|
||||
|
||||
namespace xlnt {
|
||||
|
||||
|
@ -59,7 +60,7 @@ inline bool operator==(const sheet_format_properties &lhs, const sheet_format_pr
|
|||
{
|
||||
return lhs.base_col_width == rhs.base_col_width
|
||||
&& lhs.default_column_width == rhs.default_column_width
|
||||
&& lhs.default_row_height == rhs.default_row_height
|
||||
&& detail::float_equals(lhs.default_row_height, rhs.default_row_height)
|
||||
&& lhs.dy_descent == rhs.dy_descent;
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,6 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
|||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-documentation-unknown-command") # ignore unknown commands in Javadoc-style comments
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-pragmas") # ignore Windows and GCC pragmas
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unknown-warning-option") # ignore Windows and GCC pragmas
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-float-equal") # don't warn on uses of == for fp types
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-newline-eof") # no longer an issue with post-c++11 standards which mandate include add a newline if neccesary
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-covered-switch-default") # default is often added to switches for completeness or to cover future alternatives
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-exit-time-destructors") # this is just a warning to notify that the destructor will run during exit
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
// @author: see AUTHORS file
|
||||
|
||||
#include <detail/header_footer/header_footer_code.hpp>
|
||||
#include <xlnt/utils/serialisation_utils.hpp>
|
||||
#include <detail/numeric_utils.hpp>
|
||||
|
||||
namespace xlnt {
|
||||
namespace detail {
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include <xlnt/utils/optional.hpp>
|
||||
#include <detail/implementations/format_impl.hpp>
|
||||
#include <detail/implementations/hyperlink_impl.hpp>
|
||||
#include "../numeric_utils.hpp"
|
||||
|
||||
namespace xlnt {
|
||||
namespace detail {
|
||||
|
@ -75,7 +76,7 @@ inline bool operator==(const cell_impl &lhs, const cell_impl &rhs)
|
|||
&& lhs.is_merged_ == rhs.is_merged_
|
||||
&& lhs.phonetics_visible_ == rhs.phonetics_visible_
|
||||
&& lhs.value_text_ == rhs.value_text_
|
||||
&& lhs.value_numeric_ == rhs.value_numeric_
|
||||
&& float_equals(lhs.value_numeric_, rhs.value_numeric_)
|
||||
&& lhs.formula_ == rhs.formula_
|
||||
&& lhs.hyperlink_ == rhs.hyperlink_
|
||||
&& (lhs.format_.is_set() == rhs.format_.is_set() && (!lhs.format_.is_set() || *lhs.format_.get() == *rhs.format_.get()))
|
||||
|
|
119
source/detail/numeric_utils.hpp
Normal file
119
source/detail/numeric_utils.hpp
Normal file
|
@ -0,0 +1,119 @@
|
|||
// Copyright (c) 2014-2018 Thomas Fussell
|
||||
//
|
||||
// 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, WRISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE
|
||||
//
|
||||
// @license: http://www.opensource.org/licenses/mit-license.php
|
||||
// @author: see AUTHORS file
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <xlnt/xlnt_config.hpp>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
|
||||
namespace xlnt {
|
||||
namespace detail {
|
||||
/// <summary>
|
||||
/// Takes in any number and outputs a string form of that number which will
|
||||
/// serialise and deserialise without loss of precision
|
||||
/// </summary>
|
||||
template <typename Number>
|
||||
std::string serialize_number_to_string(Number num)
|
||||
{
|
||||
// more digits and excel won't match
|
||||
constexpr int Excel_Digit_Precision = 15; //sf
|
||||
std::stringstream ss;
|
||||
ss.precision(Excel_Digit_Precision);
|
||||
ss << num;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// constexpr abs
|
||||
/// </summary>
|
||||
template <typename Number>
|
||||
constexpr Number abs(Number val)
|
||||
{
|
||||
return (val < Number{0}) ? -val : val;
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// constexpr max
|
||||
/// </summary>
|
||||
template <typename NumberL, typename NumberR>
|
||||
constexpr typename std::common_type<NumberL, NumberR>::type max(NumberL lval, NumberR rval)
|
||||
{
|
||||
return (lval < rval) ? rval : lval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// constexpr min
|
||||
/// </summary>
|
||||
template <typename NumberL, typename NumberR>
|
||||
constexpr typename std::common_type<NumberL, NumberR>::type min(NumberL lval, NumberR rval)
|
||||
{
|
||||
return (lval < rval) ? lval : rval;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Floating point equality requires a bit of fuzzing due to the imprecise nature of fp calculation
|
||||
/// References:
|
||||
/// - Several blogs/articles were referenced with the following being the most useful
|
||||
/// -- https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
|
||||
/// -- http://realtimecollisiondetection.net/blog/?p=89
|
||||
/// - Testing Frameworks {Catch2, Boost, Google}, primarily for selecting the default scale factor
|
||||
/// -- None of these even remotely agree
|
||||
/// </summary>
|
||||
template <typename EpsilonType = float, // the type to extract epsilon from
|
||||
typename LNumber, typename RNumber> // parameter types (deduced)
|
||||
bool
|
||||
float_equals(const LNumber &lhs, const RNumber &rhs,
|
||||
int epsilon_scale = 20) // scale the "fuzzy" equality. Higher value gives a more tolerant comparison
|
||||
{
|
||||
// a type that lhs and rhs can agree on
|
||||
using common_t = typename std::common_type<LNumber, RNumber>::type;
|
||||
// asserts for sane usage
|
||||
static_assert(std::is_floating_point<LNumber>::value || std::is_floating_point<RNumber>::value,
|
||||
"Using this function with two integers is just wasting time. Use ==");
|
||||
static_assert(std::numeric_limits<EpsilonType>::epsilon() < EpsilonType{1},
|
||||
"epsilon >= 1.0 will cause all comparisons to return true");
|
||||
|
||||
// NANs always compare false with themselves
|
||||
if (std::isnan(lhs) || std::isnan(rhs))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// epsilon type defaults to float because even if both args are a higher precision type
|
||||
// either or both could have been promoted by prior operations
|
||||
// if a higher precision is required, the template type can be changed
|
||||
constexpr common_t epsilon = static_cast<common_t>(std::numeric_limits<EpsilonType>::epsilon());
|
||||
// the "epsilon" then needs to be scaled into the comparison range
|
||||
// epsilon for numeric_limits is valid when abs(x) <1.0, scaling only needs to be upwards
|
||||
// in particular, this prevents a lhs of 0 from requiring an exact comparison
|
||||
// additionally, a scale factor is applied.
|
||||
common_t scaled_fuzz = epsilon_scale * epsilon * max(max(xlnt::detail::abs<common_t>(lhs),
|
||||
xlnt::detail::abs<common_t>(rhs)), // |max| of parameters.
|
||||
common_t{1}); // clamp
|
||||
return ((lhs + scaled_fuzz) >= rhs) && ((rhs + scaled_fuzz) >= lhs);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
} // namespace xlnt
|
|
@ -33,12 +33,12 @@
|
|||
#include <detail/serialization/vector_streambuf.hpp>
|
||||
#include <detail/serialization/xlsx_producer.hpp>
|
||||
#include <detail/serialization/zstream.hpp>
|
||||
#include <detail/numeric_utils.hpp>
|
||||
#include <xlnt/cell/cell.hpp>
|
||||
#include <xlnt/cell/hyperlink.hpp>
|
||||
#include <xlnt/packaging/manifest.hpp>
|
||||
#include <xlnt/utils/path.hpp>
|
||||
#include <xlnt/utils/scoped_enum_hash.hpp>
|
||||
#include <xlnt/utils/serialisation_utils.hpp>
|
||||
#include <xlnt/workbook/workbook.hpp>
|
||||
#include <xlnt/workbook/workbook_view.hpp>
|
||||
#include <xlnt/worksheet/header_footer.hpp>
|
||||
|
|
|
@ -507,12 +507,12 @@ bool izstream::read_central_header()
|
|||
// Find the header
|
||||
// NOTE: this assumes the zip file header is the last thing written to file...
|
||||
source_stream_.seekg(0, std::ios_base::end);
|
||||
auto end_position = static_cast<std::size_t>(source_stream_.tellg());
|
||||
auto end_position = source_stream_.tellg();
|
||||
|
||||
auto max_comment_size = std::uint32_t(0xffff); // max size of header
|
||||
auto read_size_before_comment = std::uint32_t(22);
|
||||
|
||||
std::uint32_t read_start = max_comment_size + read_size_before_comment;
|
||||
std::streamoff read_start = max_comment_size + read_size_before_comment;
|
||||
|
||||
if (read_start > end_position)
|
||||
{
|
||||
|
@ -536,11 +536,14 @@ bool izstream::read_central_header()
|
|||
}
|
||||
|
||||
auto found_header = false;
|
||||
std::size_t header_index = 0;
|
||||
std::streamoff header_index = 0;
|
||||
|
||||
for (std::size_t i = 0; i < static_cast<std::size_t>(read_start - 3); ++i)
|
||||
for (std::streamoff i = 0; i < read_start - 3; ++i)
|
||||
{
|
||||
if (buf[i] == 0x50 && buf[i + 1] == 0x4b && buf[i + 2] == 0x05 && buf[i + 3] == 0x06)
|
||||
if (buf[static_cast<std::size_t>(i)] == 0x50
|
||||
&& buf[static_cast<std::size_t>(i) + 1] == 0x4b
|
||||
&& buf[static_cast<std::size_t>(i) + 2] == 0x05
|
||||
&& buf[static_cast<std::size_t>(i) + 3] == 0x06)
|
||||
{
|
||||
found_header = true;
|
||||
header_index = i;
|
||||
|
@ -554,7 +557,7 @@ bool izstream::read_central_header()
|
|||
}
|
||||
|
||||
// seek to end of central header and read
|
||||
source_stream_.seekg(end_position - (read_start - static_cast<std::ptrdiff_t>(header_index)));
|
||||
source_stream_.seekg(end_position - (read_start - header_index));
|
||||
|
||||
/*auto word = */ read_int<std::uint32_t>(source_stream_);
|
||||
auto disk_number1 = read_int<std::uint16_t>(source_stream_);
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
// @license: http://www.opensource.org/licenses/mit-license.php
|
||||
// @author: see AUTHORS file
|
||||
#include <xlnt/worksheet/page_margins.hpp>
|
||||
#include "detail/numeric_utils.hpp"
|
||||
|
||||
namespace xlnt {
|
||||
|
||||
|
@ -91,11 +92,11 @@ void page_margins::footer(double footer)
|
|||
|
||||
bool page_margins::operator==(const page_margins &rhs) const
|
||||
{
|
||||
return top_ == rhs.top_
|
||||
&& left_ == rhs.left_
|
||||
&& right_ == rhs.right_
|
||||
&& header_ == rhs.header_
|
||||
&& footer_ == rhs.footer_;
|
||||
return detail::float_equals(top_, rhs.top_)
|
||||
&& detail::float_equals(left_,rhs.left_)
|
||||
&& detail::float_equals(right_, rhs.right_)
|
||||
&& detail::float_equals(header_, rhs.header_)
|
||||
&& detail::float_equals(footer_, rhs.footer_);
|
||||
}
|
||||
|
||||
} // namespace xlnt
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
// @license: http://www.opensource.org/licenses/mit-license.php
|
||||
// @author: see AUTHORS file
|
||||
#include <xlnt/worksheet/page_setup.hpp>
|
||||
#include "detail/numeric_utils.hpp"
|
||||
|
||||
namespace xlnt {
|
||||
|
||||
|
@ -114,7 +115,7 @@ bool page_setup::operator==(const page_setup &rhs) const
|
|||
&& fit_to_page_ == rhs.fit_to_page_
|
||||
&& fit_to_height_ == rhs.fit_to_height_
|
||||
&& fit_to_width_ == rhs.fit_to_width_
|
||||
&& scale_ == rhs.scale_;
|
||||
&& detail::float_equals(scale_, rhs.scale_);
|
||||
}
|
||||
|
||||
} // namespace xlnt
|
||||
|
|
|
@ -46,10 +46,11 @@
|
|||
#include <xlnt/worksheet/column_properties.hpp>
|
||||
#include <detail/constants.hpp>
|
||||
#include <detail/default_case.hpp>
|
||||
#include <detail/numeric_utils.hpp>
|
||||
#include <detail/unicode.hpp>
|
||||
#include <detail/implementations/cell_impl.hpp>
|
||||
#include <detail/implementations/workbook_impl.hpp>
|
||||
#include <detail/implementations/worksheet_impl.hpp>
|
||||
#include <detail/unicode.hpp>
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -102,7 +103,7 @@ void worksheet::create_named_range(const std::string &name, const range_referenc
|
|||
throw invalid_parameter(); //("named range name must be outside the range A1-XFD1048576");
|
||||
}
|
||||
}
|
||||
catch (xlnt::invalid_cell_reference&)
|
||||
catch (xlnt::invalid_cell_reference &)
|
||||
{
|
||||
// name is not a valid reference, that's good
|
||||
}
|
||||
|
@ -732,7 +733,8 @@ void worksheet::clear_row(row_t row)
|
|||
{
|
||||
it = d_->cell_map_.erase(it);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
@ -930,29 +932,28 @@ bool worksheet::compare(const worksheet &other, bool reference) const
|
|||
|
||||
if (d_->parent_ != other.d_->parent_) return false;
|
||||
|
||||
|
||||
for (auto &cell : d_->cell_map_)
|
||||
for (auto &cell : d_->cell_map_)
|
||||
{
|
||||
if (other.d_->cell_map_.find(cell.first) == other.d_->cell_map_.end())
|
||||
{
|
||||
if (other.d_->cell_map_.find(cell.first) == other.d_->cell_map_.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
xlnt::cell this_cell(&cell.second);
|
||||
xlnt::cell other_cell(&other.d_->cell_map_[cell.first]);
|
||||
|
||||
if (this_cell.data_type() != other_cell.data_type())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this_cell.data_type() == xlnt::cell::type::number
|
||||
&& std::fabs(this_cell.value<double>() - other_cell.value<double>()) > 0.0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
xlnt::cell this_cell(&cell.second);
|
||||
xlnt::cell other_cell(&other.d_->cell_map_[cell.first]);
|
||||
|
||||
if (this_cell.data_type() != other_cell.data_type())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this_cell.data_type() == xlnt::cell::type::number
|
||||
&& !detail::float_equals(this_cell.value<double>(), other_cell.value<double>()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// todo: missing some comparisons
|
||||
|
||||
if (d_->auto_filter_ == other.d_->auto_filter_ && d_->views_ == other.d_->views_
|
||||
|
@ -1186,12 +1187,12 @@ bool worksheet::has_phonetic_properties() const
|
|||
return d_->phonetic_properties_.is_set();
|
||||
}
|
||||
|
||||
const phonetic_pr& worksheet::phonetic_properties() const
|
||||
const phonetic_pr &worksheet::phonetic_properties() const
|
||||
{
|
||||
return d_->phonetic_properties_.get();
|
||||
}
|
||||
|
||||
void worksheet::phonetic_properties(const phonetic_pr& phonetic_props)
|
||||
void worksheet::phonetic_properties(const phonetic_pr &phonetic_props)
|
||||
{
|
||||
d_->phonetic_properties_.set(phonetic_props);
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ endif()
|
|||
|
||||
file(GLOB CELL_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/cell/*.cpp)
|
||||
file(GLOB DRAWING_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/drawing/*.cpp)
|
||||
file(GLOB DETAIL_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/detail/*.cpp)
|
||||
file(GLOB PACKAGING_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/packaging/*.cpp)
|
||||
file(GLOB STYLES_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/styles/*.cpp)
|
||||
file(GLOB UTILS_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/utils/*.cpp)
|
||||
|
@ -25,6 +26,7 @@ file(GLOB WORKSHEET_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/worksheet/*.cpp)
|
|||
set(TESTS
|
||||
${CELL_TESTS}
|
||||
${DRAWING_TESTS}
|
||||
${DETAIL_TESTS}
|
||||
${PACKAGING_TESTS}
|
||||
${STYLES_TESTS}
|
||||
${UTILS_TESTS}
|
||||
|
@ -61,6 +63,7 @@ endif()
|
|||
source_group(helpers FILES ${HELPERS})
|
||||
source_group(runner FILES ${RUNNER})
|
||||
source_group(tests\\cell FILES ${CELL_TESTS})
|
||||
source_group(tests\\detail FILES ${DETAIL_TESTS})
|
||||
source_group(tests\\packaging FILES ${PACKAGING_TESTS})
|
||||
source_group(tests\\serialization FILES ${SERIALIZATION_TESTS})
|
||||
source_group(tests\\styles FILES ${STYLES_TESTS})
|
||||
|
|
222
tests/detail/numeric_util_test_suite.cpp
Normal file
222
tests/detail/numeric_util_test_suite.cpp
Normal file
|
@ -0,0 +1,222 @@
|
|||
// Copyright (c) 2014-2018 Thomas Fussell
|
||||
//
|
||||
// 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, WRISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE
|
||||
//
|
||||
// @license: http://www.opensource.org/licenses/mit-license.php
|
||||
// @author: see AUTHORS file
|
||||
|
||||
#include "../../source/detail/numeric_utils.hpp"
|
||||
#include <helpers/test_suite.hpp>
|
||||
|
||||
class numeric_test_suite : public test_suite
|
||||
{
|
||||
public:
|
||||
numeric_test_suite()
|
||||
{
|
||||
register_test(test_serialise_number);
|
||||
register_test(test_float_equals_zero);
|
||||
register_test(test_float_equals_large);
|
||||
register_test(test_float_equals_fairness);
|
||||
register_test(test_min);
|
||||
register_test(test_max);
|
||||
register_test(test_abs);
|
||||
}
|
||||
|
||||
void test_serialise_number()
|
||||
{
|
||||
// excel serialises numbers as floating point values with <= 15 digits of precision
|
||||
xlnt_assert(xlnt::detail::serialize_number_to_string(1) == "1");
|
||||
// trailing zeroes are ignored
|
||||
xlnt_assert(xlnt::detail::serialize_number_to_string(1.0) == "1");
|
||||
xlnt_assert(xlnt::detail::serialize_number_to_string(1.0f) == "1");
|
||||
// one to 1 relation
|
||||
xlnt_assert(xlnt::detail::serialize_number_to_string(1.23456) == "1.23456");
|
||||
xlnt_assert(xlnt::detail::serialize_number_to_string(1.23456789012345) == "1.23456789012345");
|
||||
xlnt_assert(xlnt::detail::serialize_number_to_string(123456.789012345) == "123456.789012345");
|
||||
xlnt_assert(xlnt::detail::serialize_number_to_string(1.23456789012345e+67) == "1.23456789012345e+67");
|
||||
xlnt_assert(xlnt::detail::serialize_number_to_string(1.23456789012345e-67) == "1.23456789012345e-67");
|
||||
}
|
||||
|
||||
void test_float_equals_zero()
|
||||
{
|
||||
// comparing relatively small numbers (2.3e-6) with 0 will be true by default
|
||||
const float comp_val = 2.3e-6; // about the largest difference allowed by default
|
||||
xlnt_assert(0.f != comp_val); // fail because not exactly equal
|
||||
xlnt_assert(xlnt::detail::float_equals(0.0, comp_val));
|
||||
xlnt_assert(xlnt::detail::float_equals(0.0, -comp_val));
|
||||
// fail because diff is out of bounds for fuzzy equality
|
||||
xlnt_assert(!xlnt::detail::float_equals(0.0, comp_val + 0.1e-6));
|
||||
xlnt_assert(!xlnt::detail::float_equals(0.0, -(comp_val + 0.1e-6)));
|
||||
// if the bounds of comparison are too loose, there are two tweakable knobs to tighten the comparison up
|
||||
//==========================================================
|
||||
// #1: reduce the epsilon_scale (default is 20)
|
||||
// This can bring the range down to FLT_EPSILON (scale factor of 1)
|
||||
xlnt_assert(!xlnt::detail::float_equals(0.0, comp_val, 10));
|
||||
const float closer_comp_val = 1.1e-6;
|
||||
xlnt_assert(xlnt::detail::float_equals(0.0, closer_comp_val, 10));
|
||||
xlnt_assert(!xlnt::detail::float_equals(0.0, closer_comp_val + 0.1e-6, 10));
|
||||
xlnt_assert(xlnt::detail::float_equals(0.0, -closer_comp_val, 10));
|
||||
xlnt_assert(!xlnt::detail::float_equals(0.0, -(closer_comp_val + 0.1e-6), 10));
|
||||
//==========================================================
|
||||
// #2: specify the epsilon source as a higher precision type (e.g. double)
|
||||
// This makes the epsilon range quite significantly less
|
||||
xlnt_assert(!xlnt::detail::float_equals<double>(0.0, comp_val));
|
||||
xlnt_assert(!xlnt::detail::float_equals<double>(0.0, closer_comp_val));
|
||||
const float tiny_comp_val = 4.4e-15;
|
||||
xlnt_assert(xlnt::detail::float_equals<double>(0.0, tiny_comp_val));
|
||||
xlnt_assert(!xlnt::detail::float_equals<double>(0.0, tiny_comp_val + 0.1e-15));
|
||||
xlnt_assert(xlnt::detail::float_equals<double>(0.0, -tiny_comp_val));
|
||||
xlnt_assert(!xlnt::detail::float_equals<double>(0.0, -(tiny_comp_val + 0.1e-15)));
|
||||
//==========================================================
|
||||
// #3: combine #1 & #2
|
||||
// for the tightest default precision, double with a scale of 1
|
||||
xlnt_assert(!xlnt::detail::float_equals<double>(0.0, comp_val, 1));
|
||||
xlnt_assert(!xlnt::detail::float_equals<double>(0.0, closer_comp_val, 1));
|
||||
xlnt_assert(!xlnt::detail::float_equals<double>(0.0, tiny_comp_val, 1));
|
||||
const float really_tiny_comp_val = 2.2e-16; // the limit is +/- std::numeric_limits<double>::epsilon()
|
||||
xlnt_assert(xlnt::detail::float_equals<double>(0.0, really_tiny_comp_val, 1));
|
||||
xlnt_assert(!xlnt::detail::float_equals<double>(0.0, really_tiny_comp_val + 0.1e-16, 1));
|
||||
xlnt_assert(xlnt::detail::float_equals<double>(0.0, -really_tiny_comp_val, 1));
|
||||
xlnt_assert(!xlnt::detail::float_equals<double>(0.0, -(really_tiny_comp_val + 0.1e-16), 1));
|
||||
//==========================================================
|
||||
// in the world of floats, 2.2e-16 is still significantly different to 0.f (smallest representable float is around 1e-38)
|
||||
// if comparisons are known to involve extremely small numbers (such that +/- 2.2e-16 is too large a band),
|
||||
// a type that specialises std::numeric_limits::epsilon may be passed as the first template parameter
|
||||
// the type itself doesn't actually need to have any behaviour as it is only used as the source for epsilon
|
||||
// struct super_precise{};
|
||||
// namespace std {
|
||||
// template<> numeric_limits<super_precise> {
|
||||
// double epsilon() {
|
||||
// return 1e-30;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// float_equals<double>(0.0, 2e-30, 1); // returns true
|
||||
// float_equals<super_precise>(0.0, 2e-30, 1); // returns false
|
||||
}
|
||||
|
||||
void test_float_equals_large()
|
||||
{
|
||||
const float compare_to = 20e6;
|
||||
// fp math with arguments of different magnitudes is wierd
|
||||
xlnt_assert(compare_to == compare_to + 1); // x == x + 1 ...
|
||||
xlnt_assert(compare_to != compare_to + 10); // x != x + 10
|
||||
xlnt_assert(compare_to != compare_to - 10); // x != x - 10
|
||||
// if the same epsilon was used for comparison of large values as the values around one
|
||||
// we'd have all the issues around zero again
|
||||
xlnt_assert(xlnt::detail::float_equals(compare_to, compare_to + 49));
|
||||
xlnt_assert(!xlnt::detail::float_equals(compare_to, compare_to + 50));
|
||||
xlnt_assert(xlnt::detail::float_equals(compare_to, compare_to - 49));
|
||||
xlnt_assert(!xlnt::detail::float_equals(compare_to, compare_to - 50));
|
||||
// float_equals also scales epsilon up to match the magnitude of its arguments
|
||||
// all the same options are available for increasing/decreasing the precision of the comparison
|
||||
// however the the epsilon source should always be of equal or lesser precision than the arguments when away from zero
|
||||
}
|
||||
|
||||
void test_float_equals_nan()
|
||||
{
|
||||
const float nan = std::nan("");
|
||||
// nans always compare false
|
||||
xlnt_assert(!xlnt::detail::float_equals(nan, 0.f));
|
||||
xlnt_assert(!xlnt::detail::float_equals(nan, nan));
|
||||
xlnt_assert(!xlnt::detail::float_equals(nan, 1000.f));
|
||||
}
|
||||
|
||||
void test_float_equals_fairness()
|
||||
{
|
||||
// tests for parameter ordering dependency
|
||||
// (lhs ~= rhs) == (rhs ~= lhs)
|
||||
const double test_val = 1.0;
|
||||
const double test_diff_pass = 1.192092e-07; // should all pass with this
|
||||
const double test_diff = 1.192093e-07; // difference enough to provide different results if the comparison is not "fair"
|
||||
const double test_diff_fails = 1.192094e-07; // should all fail with this
|
||||
|
||||
// test_diff_pass
|
||||
xlnt_assert(xlnt::detail::float_equals<float>((test_val + test_diff_pass), test_val, 1));
|
||||
xlnt_assert(xlnt::detail::float_equals<float>(test_val, (test_val + test_diff_pass), 1));
|
||||
xlnt_assert(xlnt::detail::float_equals<float>(-(test_val + test_diff_pass), -test_val, 1));
|
||||
xlnt_assert(xlnt::detail::float_equals<float>(-test_val, -(test_val + test_diff_pass), 1));
|
||||
// test_diff
|
||||
xlnt_assert(xlnt::detail::float_equals<float>((test_val + test_diff), test_val, 1));
|
||||
xlnt_assert(xlnt::detail::float_equals<float>(test_val, (test_val + test_diff), 1));
|
||||
xlnt_assert(xlnt::detail::float_equals<float>(-(test_val + test_diff), -test_val, 1));
|
||||
xlnt_assert(xlnt::detail::float_equals<float>(-test_val, -(test_val + test_diff), 1));
|
||||
// test_diff_fails
|
||||
xlnt_assert(!xlnt::detail::float_equals<float>((test_val + test_diff_fails), test_val, 1));
|
||||
xlnt_assert(!xlnt::detail::float_equals<float>(test_val, (test_val + test_diff_fails), 1));
|
||||
xlnt_assert(!xlnt::detail::float_equals<float>(-(test_val + test_diff_fails), -test_val, 1));
|
||||
xlnt_assert(!xlnt::detail::float_equals<float>(-test_val, -(test_val + test_diff_fails), 1));
|
||||
}
|
||||
|
||||
void test_min()
|
||||
{
|
||||
// simple
|
||||
xlnt_assert(xlnt::detail::min(0, 1) == 0);
|
||||
xlnt_assert(xlnt::detail::min(1, 0) == 0);
|
||||
xlnt_assert(xlnt::detail::min(0.0, 1) == 0.0); // comparisons between different types just work
|
||||
xlnt_assert(xlnt::detail::min(1, 0.0) == 0.0);
|
||||
// negative numbers
|
||||
xlnt_assert(xlnt::detail::min(0, -1) == -1.0);
|
||||
xlnt_assert(xlnt::detail::min(-1, 0) == -1.0);
|
||||
xlnt_assert(xlnt::detail::min(0.0, -1) == -1.0);
|
||||
xlnt_assert(xlnt::detail::min(-1, 0.0) == -1.0);
|
||||
// no zeroes
|
||||
xlnt_assert(xlnt::detail::min(10, -10) == -10.0);
|
||||
xlnt_assert(xlnt::detail::min(-10, 10) == -10.0);
|
||||
xlnt_assert(xlnt::detail::min(10.0, -10) == -10.0);
|
||||
xlnt_assert(xlnt::detail::min(-10, 10.0) == -10.0);
|
||||
|
||||
static_assert(xlnt::detail::min(-10, 10.0) == -10.0, "constexpr");
|
||||
}
|
||||
|
||||
void test_max()
|
||||
{
|
||||
// simple
|
||||
xlnt_assert(xlnt::detail::max(0, 1) == 1);
|
||||
xlnt_assert(xlnt::detail::max(1, 0) == 1);
|
||||
xlnt_assert(xlnt::detail::max(0.0, 1) == 1.0); // comparisons between different types just work
|
||||
xlnt_assert(xlnt::detail::max(1, 0.0) == 1.0);
|
||||
// negative numbers
|
||||
xlnt_assert(xlnt::detail::max(0, -1) == 0.0);
|
||||
xlnt_assert(xlnt::detail::max(-1, 0) == 0.0);
|
||||
xlnt_assert(xlnt::detail::max(0.0, -1) == 0.0);
|
||||
xlnt_assert(xlnt::detail::max(-1, 0.0) == 0.0);
|
||||
// no zeroes
|
||||
xlnt_assert(xlnt::detail::max(10, -10) == 10.0);
|
||||
xlnt_assert(xlnt::detail::max(-10, 10) == 10.0);
|
||||
xlnt_assert(xlnt::detail::max(10.0, -10) == 10.0);
|
||||
xlnt_assert(xlnt::detail::max(-10, 10.0) == 10.0);
|
||||
|
||||
static_assert(xlnt::detail::max(-10, 10.0) == 10.0, "constexpr");
|
||||
}
|
||||
|
||||
void test_abs()
|
||||
{
|
||||
xlnt_assert(xlnt::detail::abs(0) == 0);
|
||||
xlnt_assert(xlnt::detail::abs(1) == 1);
|
||||
xlnt_assert(xlnt::detail::abs(-1) == 1);
|
||||
|
||||
xlnt_assert(xlnt::detail::abs(0.0) == 0.0);
|
||||
xlnt_assert(xlnt::detail::abs(1.5) == 1.5);
|
||||
xlnt_assert(xlnt::detail::abs(-1.5) == 1.5);
|
||||
|
||||
static_assert(xlnt::detail::abs(-1.23) == 1.23, "constexpr");
|
||||
}
|
||||
};
|
||||
static numeric_test_suite x;
|
Loading…
Reference in New Issue
Block a user