Я обнаружил, что Google C ++ Testing Framework содержит хорошую кросс-платформенную реализацию на основе шаблонов AlmostEqual2sComplement, которая работает как по удвоению, так и по плаванию. Учитывая, что он выпущен под лицензией BSD, использование его в вашем собственном коде не должно быть проблемой, если вы сохраняете лицензию. Я извлек приведенный ниже код из http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.h https: / /github.com/google/googletest/blob/master/googletest/include/gtest/internal/gtest-internal.h и добавила лицензию сверху.
Обязательно #define GTEST_OS_WINDOWS к некоторому значению (или изменить код, в котором он используется для чего-то, что соответствует вашей кодовой базе), это лицензия BSD в конце концов).
Пример использования:
double left = // something
double right = // something
const FloatingPoint lhs(left), rhs(right);
if (lhs.AlmostEquals(rhs)) {
//they're equal!
}
Вот код:
// Copyright 2005, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee)
//
// The Google C++ Testing Framework (Google Test)
// This template class serves as a compile-time function from size to
// type. It maps a size in bytes to a primitive type with that
// size. e.g.
//
// TypeWithSize<4>::UInt
//
// is typedef-ed to be unsigned int (unsigned integer made up of 4
// bytes).
//
// Such functionality should belong to STL, but I cannot find it
// there.
//
// Google Test uses this class in the implementation of floating-point
// comparison.
//
// For now it only handles UInt (unsigned int) as that's all Google Test
// needs. Other types can be easily added in the future if need
// arises.
template
class TypeWithSize {
public:
// This prevents the user from using TypeWithSize with incorrect
// values of N.
typedef void UInt;
};
// The specialization for size 4.
template <>
class TypeWithSize<4> {
public:
// unsigned int has size 4 in both gcc and MSVC.
//
// As base/basictypes.h doesn't compile on Windows, we cannot use
// uint32, uint64, and etc here.
typedef int Int;
typedef unsigned int UInt;
};
// The specialization for size 8.
template <>
class TypeWithSize<8> {
public:
#if GTEST_OS_WINDOWS
typedef __int64 Int;
typedef unsigned __int64 UInt;
#else
typedef long long Int; // NOLINT
typedef unsigned long long UInt; // NOLINT
#endif // GTEST_OS_WINDOWS
};
// This template class represents an IEEE floating-point number
// (either single-precision or double-precision, depending on the
// template parameters).
//
// The purpose of this class is to do more sophisticated number
// comparison. (Due to round-off error, etc, it's very unlikely that
// two floating-points will be equal exactly. Hence a naive
// comparison by the == operation often doesn't work.)
//
// Format of IEEE floating-point:
//
// The most-significant bit being the leftmost, an IEEE
// floating-point looks like
//
// sign_bit exponent_bits fraction_bits
//
// Here, sign_bit is a single bit that designates the sign of the
// number.
//
// For float, there are 8 exponent bits and 23 fraction bits.
//
// For double, there are 11 exponent bits and 52 fraction bits.
//
// More details can be found at
// http://en.wikipedia.org/wiki/IEEE_floating-point_standard.
//
// Template parameter:
//
// RawType: the raw floating-point type (either float or double)
template
class FloatingPoint {
public:
// Defines the unsigned integer type that has the same size as the
// floating point number.
typedef typename TypeWithSize::UInt Bits;
// Constants.
// # of bits in a number.
static const size_t kBitCount = 8*sizeof(RawType);
// # of fraction bits in a number.
static const size_t kFractionBitCount =
std::numeric_limits::digits - 1;
// # of exponent bits in a number.
static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount;
// The mask for the sign bit.
static const Bits kSignBitMask = static_cast(1) << (kBitCount - 1);
// The mask for the fraction bits.
static const Bits kFractionBitMask =
~static_cast(0) >> (kExponentBitCount + 1);
// The mask for the exponent bits.
static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask);
// How many ULP's (Units in the Last Place) we want to tolerate when
// comparing two numbers. The larger the value, the more error we
// allow. A 0 value means that two numbers must be exactly the same
// to be considered equal.
//
// The maximum error of a single floating-point operation is 0.5
// units in the last place. On Intel CPU's, all floating-point
// calculations are done with 80-bit precision, while double has 64
// bits. Therefore, 4 should be enough for ordinary use.
//
// See the following article for more details on ULP:
// http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm.
static const size_t kMaxUlps = 4;
// Constructs a FloatingPoint from a raw floating-point number.
//
// On an Intel CPU, passing a non-normalized NAN (Not a Number)
// around may change its bits, although the new value is guaranteed
// to be also a NAN. Therefore, don't expect this constructor to
// preserve the bits in x when x is a NAN.
explicit FloatingPoint(const RawType& x) { u_.value_ = x; }
// Static methods
// Reinterprets a bit pattern as a floating-point number.
//
// This function is needed to test the AlmostEquals() method.
static RawType ReinterpretBits(const Bits bits) {
FloatingPoint fp(0);
fp.u_.bits_ = bits;
return fp.u_.value_;
}
// Returns the floating-point number that represent positive infinity.
static RawType Infinity() {
return ReinterpretBits(kExponentBitMask);
}
// Non-static methods
// Returns the bits that represents this number.
const Bits &bits() const { return u_.bits_; }
// Returns the exponent bits of this number.
Bits exponent_bits() const { return kExponentBitMask & u_.bits_; }
// Returns the fraction bits of this number.
Bits fraction_bits() const { return kFractionBitMask & u_.bits_; }
// Returns the sign bit of this number.
Bits sign_bit() const { return kSignBitMask & u_.bits_; }
// Returns true iff this is NAN (not a number).
bool is_nan() const {
// It's a NAN if the exponent bits are all ones and the fraction
// bits are not entirely zeros.
return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0);
}
// Returns true iff this number is at most kMaxUlps ULP's away from
// rhs. In particular, this function:
//
// - returns false if either number is (or both are) NAN.
// - treats really large numbers as almost equal to infinity.
// - thinks +0.0 and -0.0 are 0 DLP's apart.
bool AlmostEquals(const FloatingPoint& rhs) const {
// The IEEE standard says that any comparison operation involving
// a NAN must return false.
if (is_nan() || rhs.is_nan()) return false;
return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_)
<= kMaxUlps;
}
private:
// The data type used to store the actual floating-point number.
union FloatingPointUnion {
RawType value_; // The raw floating-point number.
Bits bits_; // The bits that represent the number.
};
// Converts an integer from the sign-and-magnitude representation to
// the biased representation. More precisely, let N be 2 to the
// power of (kBitCount - 1), an integer x is represented by the
// unsigned number x + N.
//
// For instance,
//
// -N + 1 (the most negative number representable using
// sign-and-magnitude) is represented by 1;
// 0 is represented by N; and
// N - 1 (the biggest number representable using
// sign-and-magnitude) is represented by 2N - 1.
//
// Read http://en.wikipedia.org/wiki/Signed_number_representations
// for more details on signed number representations.
static Bits SignAndMagnitudeToBiased(const Bits &sam) {
if (kSignBitMask & sam) {
// sam represents a negative number.
return ~sam + 1;
} else {
// sam represents a positive number.
return kSignBitMask | sam;
}
}
// Given two numbers in the sign-and-magnitude representation,
// returns the distance between them as an unsigned number.
static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1,
const Bits &sam2) {
const Bits biased1 = SignAndMagnitudeToBiased(sam1);
const Bits biased2 = SignAndMagnitudeToBiased(sam2);
return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1);
}
FloatingPointUnion u_;
};
EDIT: Это сообщение 4 года. Это, вероятно, все еще актуально, и код хорош, но некоторые люди нашли улучшения. Лучше всего получить последнюю версию AlmostEquals
прямо из исходного кода Google Test, а не ту, которую я вставил здесь.
Это просто часть ужасающего беспорядка, который является API дат / времени Java. Перечислить, что с ним не так, займет очень много времени (и я уверен, что не знаю половины проблем). Разумеется, работа с датами и временем сложна, но все равно.
Сделайте себе одолжение и используйте Joda Time вместо, или, возможно, JSR-310 .
EDIT: Что касается причин, почему, как отмечено в других ответах, это может быть связано с старыми API-интерфейсами C или просто с общим чувством начала всего от 0 ... за исключением того, что дни начинаются с 1 , конечно. Я сомневаюсь, что кто-то, кто не был в оригинальной команде по внедрению, действительно мог объяснить причины, - но опять же я настоятельно призывал читателей не беспокоиться о том, почему были приняты плохие решения, чтобы посмотреть на всю гамму гадости в java.util.Calendar
и найти что-то лучшее.
Одна точка, которая является в пользу использования индексов на основе 0, состоит в том, что она облегчает такие вещи, как «массивы имен»:
// I "know" there are 12 months
String[] monthNames = new String[12]; // and populate...
String name = monthNames[calendar.get(Calendar.MONTH)];
Конечно, это не удается, как только вы получите календарь с 13 месяцами ... но, по крайней мере, указанный размер - это количество ожидаемых месяцев.
Это isn ' t a хорошая причина, но это причина ...
EDIT: В качестве комментариев типа запросов есть некоторые идеи о том, что я считаю неправильным Дата / Календарь:
Date
и Calendar
как разные вещи, но отсутствует разделение «локальных» и «зональных» значений, а также дата / время и дата против времени Date.toString()
, которая всегда использует локальный часовой пояс системы (это путают многие Stack Переполнение пользователей до сих пор) В Java 8 появился новый API Дата / Время JSR 310 , который более разумен. Спектр свинца такой же, как и основной автор JodaTime, и они имеют много похожих концепций и шаблонов.
Поскольку математика с месяцами намного проще.
1 месяц после декабря - январь, но, чтобы понять это, вы должны были бы взять номер месяца и сделать математику
12 + 1 = 13 // What month is 13?
Я знаю! Я могу исправить это быстро, используя модуль 12.
(12 + 1) % 12 = 1
Это работает отлично в течение 11 месяцев до ноября ...
(11 + 1) % 12 = 0 // What month is 0?
Вы можете сделать все это работаем снова, вычитая 1 до того, как вы добавите месяц, затем выполните свой модуль и, наконец, добавьте 1 обратно ... aka обходите основную проблему.
((11 - 1 + 1) % 12) + 1 = 12 // Lots of magical numbers!
Теперь давайте подумаем о проблеме с месяцами 0 - 11.
(0 + 1) % 12 = 1 // February
(1 + 1) % 12 = 2 // March
(2 + 1) % 12 = 3 // April
(3 + 1) % 12 = 4 // May
(4 + 1) % 12 = 5 // June
(5 + 1) % 12 = 6 // July
(6 + 1) % 12 = 7 // August
(7 + 1) % 12 = 8 // September
(8 + 1) % 12 = 9 // October
(9 + 1) % 12 = 10 // November
(10 + 1) % 12 = 11 // December
(11 + 1) % 12 = 0 // January
Все месяцы работают одинаково, а работа вокруг не нужна.
Month.FEBRUARY.getValue() // February → 2.
2
Подробности
Ответа на этот вопрос Jon Skeet .
Теперь у нас есть современная замена для этих неприятных старых устаревших классов времени: классы java.time .
java.time.Month
Среди этих классов есть
Month
перечисление . Перечисление содержит один или несколько предопределенных объектов, объекты, которые автоматически создаются при загрузке класса. НаMonth
у нас есть дюжина таких объектов, каждая из которых имеет имя:JANUARY
,FEBRUARY
,MARCH
и т. Д. Каждая из них является константой классаstatic final public
. Вы можете использовать и передавать эти объекты в любом месте вашего кода. Пример:someMethod( Month.AUGUST )
К счастью, они имеют правильную нумерацию, 1-12, где 1 - январь, а 12 - декабрь.
Получить объект
Month
для определенного месяца ( 1-12).Month month = Month.of( 2 ); // 2 → February.
. Идя в другом направлении, спросите объект
Month
для его номера месяца.int monthNumber = Month.FEBRUARY.getValue(); // February → 2.
Многие другие удобные методы для этого класса, такие как зная количество дней в каждом месяце . Класс может даже генерировать локализованное имя месяца.
Вы можете получить локализованное имя месяца в разных длинах или аббревиатурах.
< blockquote>String output = Month.FEBRUARY.getDisplayName( TextStyle.FULL , Locale.CANADA_FRENCH );
février
Кроме того, вы должны передавать объекты этого перечисления вокруг своей базы кода, а не просто целые числа. Это обеспечивает безопасность типов, обеспечивает допустимый диапазон значений и делает ваш код более самодокументированным. См. Учебник Oracle , если он не знаком с удивительно мощным средством перечисления в Java.
Вы также можете найти полезные
Year
иYearMonth
.
О java.time
Структура java.time встроена в Java 8 и более поздние версии. Эти классы вытесняют неприятные старые классы времени legacy , такие как
java.util.Date
,.Calendar
и & amp;java.text.SimpleDateFormat
.Проект Joda-Time , теперь в режиме обслуживания , советует перейти на java.time.
Чтобы узнать больше, см. учебное пособие Oracle . И поиск Stack Overflow для многих примеров и объяснений. Спецификация JSR 310 .
Где получить классы java.time?
- Java SE 8 и SE 9 и более поздние версии. Часть стандартного Java API с интегрированной реализацией. Java 9 добавляет некоторые незначительные функции и исправления.
- Java SE 6 и SE 7 Большая часть функций java.time возвращается в Java 6 & amp; 7 в ThreeTen-Backport .
- Android Проект ThreeTenABP адаптирует ThreeTen-Backport (упомянутый выше) специально для Android. См. Как использовать ... .
Проект ThreeTen-Extra расширяет java.time с дополнительными классами. Этот проект является доказательством возможных будущих дополнений к java.time. Здесь вы можете найти полезные классы, такие как
Interval
,YearWeek
,YearQuarter
и more .
Потому что программисты одержимы индексами 0. Хорошо, это немного сложнее: имеет смысл, когда вы работаете с логикой более низкого уровня, чтобы использовать индексирование на основе 0. Но по большому счету, я по-прежнему придерживаюсь своего первого предложения.
Для меня никто не объясняет это лучше, чем mindpro.com :
Gotchas
java.util.GregorianCalendar
имеет гораздо меньше ошибок и ошибок, чем классold java.util.Date
, но он все еще не является пикником.Если бы были программисты, когда впервые предлагалось переход на летнее время, они наложили бы вето на него как на безумного и неразрешимого. При дневном свете существует фундаментальная двусмысленность. Осенью, когда вы устанавливаете часы на один час в 2 часа ночи, есть два разных момента времени, которые называются 1:30 AM по местному времени. Вы можете рассказать им обособленно, только если вы записываете, планируете ли вы дневное или стандартное время с чтением.
К сожалению, нет способа рассказать
GregorianCalendar
, который вы намеревались. Вы должны прибегать к тому, чтобы сообщать местное время с манекеном UTC TimeZone, чтобы избежать двусмысленности. Программисты обычно закрывают глаза на эту проблему и просто надеются, что в этот час никто ничего не делает.Ошибка тысячелетия. Ошибки все еще не из классов Calendar. Даже в JDK (Java Development Kit) 1.3 есть ошибка 2001 года. Рассмотрим следующий код:
GregorianCalendar gc = new GregorianCalendar(); gc.setLenient( false ); /* Bug only manifests if lenient set false */ gc.set( 2001, 1, 1, 1, 0, 0 ); int year = gc.get ( Calendar.YEAR ); /* throws exception */
Ошибка исчезла в 7 утра 2001/01/01 для MST.
GregorianCalendar
управляется гигантским кучей нетипизированной int magic константы. Этот метод полностью уничтожает любую надежду на проверку ошибок во время компиляции. Например, чтобы получить месяц, который вы используете,GregorianCalendar. get(Calendar.MONTH));
GregorianCalendar
имеет исходныйGregorianCalendar.get(Calendar.ZONE_OFFSET)
и дневной сбереженияGregorianCalendar. get( Calendar. DST_OFFSET)
, но не позволяет получить фактическое смещение часового пояса. Вы должны получить эти два отдельно и добавить их вместе.
GregorianCalendar.set( year, month, day, hour, minute)
не устанавливает секунды в 0.
DateFormat
иGregorianCalendar
не сливаются должным образом. Вы должны указать календарь дважды, косвенно, как Date.Если пользователь не настроил свой часовой пояс правильно, он по умолчанию будет тихо или PST или GMT.
В GregorianCalendar, Месяцы нумеруются начиная с января = 0, а не 1, как это делают все остальные на планете. Но дни начинаются с 1, как и дни недели с воскресеньем = 1, понедельник = 2, ... Суббота = 7. Тем не менее DateFormat. parse ведет себя традиционным способом с января = 1.
Он точно не определен как нуль как таковой, он определен как Calendar.January. Это проблема использования ints как констант, а не перечислений. Calendar.January == 0.
Лично я принял странность API календаря Java как признак того, что мне нужно было отвлечься от григорианского мышления и попытаться более агрессивно программировать в этом отношении. В частности, я снова научился избегать жестко закодированных констант для таких вещей, как месяцы.
Какое из следующего, скорее всего, будет правильным?
if (date.getMonth() == 3) out.print("March");
if (date.getMonth() == Calendar.MARCH) out.print("March");
Это иллюстрирует одну вещь, которая раздражает меня немного о Joda Time - это может побудить программистов думать в терминах жестко закодированных констант. (Только немного, но это не так, как если бы Joda заставлял программистов плохо программировать.)
Возможно, потому, что C "struct tm" делает то же самое.
На это было много ответов, но я дам свое мнение по этому вопросу в любом случае. Причина этого нечетного поведения, как говорилось ранее, исходит от POSIX C time.h
, где указаны месяцы, когда они хранятся в int с диапазоном 0-11. Чтобы объяснить, почему, посмотрите на это так: годы и дни считаются числами на разговорном языке, но у месяцев есть свои имена. Поэтому, поскольку январь является первым месяцем, он будет сохранен как смещение 0, первый элемент массива. monthname[JANUARY]
будет "January"
. Первый месяц в году - это элемент массива первого месяца.
Число дней, с другой стороны, поскольку у них нет имен, сохранение их в int как 0-30 будет путать, добавьте много day+1
инструкций для вывода и, конечно же, склонны к большому количеству ошибок.
Как говорится, непоследовательность запутывает, особенно в javascript (который также унаследовал эту «функцию») язык сценариев, где это должно быть абстрагировано далеко от langague.
TL; DR: Поскольку в месяцах имена и дни месяца этого не делают.
В дополнение к ответу ланды DannySmurf, я добавлю, что это побуждает вас использовать константы, такие как Calendar.JANUARY
.
Языки C на языке копируют C в некоторой степени. Структура tm
(определенная в time.h
) имеет целочисленное поле tm_mon
с диапазоном (commented) от 0-11.
Языки на языке C начинаются с массива с индексом 0. Так что это было удобно для вывода строки в массиве имен месяцев, а tm_mon
в качестве индекса.
Поскольку все начинается с 0. Это основной факт программирования на Java. Если бы одно было отклоняться от этого, то это привело бы к целому путанице. Давайте не будем спорить о их формировании и кодексах с ними.
Я бы сказал, лень. Массивы начинаются с 0 (все это знают); месяцы года - это массив, который заставляет меня поверить, что какой-то инженер из Sun просто не потрудился поставить эту маленькую мелочь в код Java.
Поскольку языковая запись сложнее, чем выглядит, а время обработки в частности намного сложнее, чем думают многие. Для небольшой части проблемы (на самом деле, а не Java) см. Видео на YouTube «Проблема со временем и часовыми поясами - компьютерная печать» на странице https://www.youtube.com/watch?v=-5wpm -gesOY . Не удивляйтесь, если ваша голова упадет от смеха в замешательстве.