// © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html /*********************************************************************** * COPYRIGHT: * Copyright (c) 1997-2015, International Business Machines Corporation * and others. All Rights Reserved. ***********************************************************************/ #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING #include "nmfmtrt.h" #include "unicode/dcfmtsym.h" #include "unicode/decimfmt.h" #include "unicode/locid.h" #include "putilimp.h" #include "cstring.h" #include #include // for sprintf #include // ***************************************************************************** // class NumberFormatRoundTripTest // ***************************************************************************** UBool NumberFormatRoundTripTest::verbose = FALSE; UBool NumberFormatRoundTripTest::STRING_COMPARE = TRUE; UBool NumberFormatRoundTripTest::EXACT_NUMERIC_COMPARE = FALSE; UBool NumberFormatRoundTripTest::DEBUG_VAR = FALSE; double NumberFormatRoundTripTest::MAX_ERROR = 1e-14; double NumberFormatRoundTripTest::max_numeric_error = 0.0; double NumberFormatRoundTripTest::min_numeric_error = 1.0; #define CASE(id,test) case id: name = #test; if (exec) { logln(#test "---"); logln((UnicodeString)""); test(); } break; void NumberFormatRoundTripTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) { // if (exec) logln((UnicodeString)"TestSuite NumberFormatRoundTripTest"); switch (index) { CASE(0, start) default: name = ""; break; } } UBool NumberFormatRoundTripTest::failure(UErrorCode status, const char* msg, UBool possibleDataError) { if(U_FAILURE(status)) { if (possibleDataError) { dataerrln(UnicodeString("FAIL: ") + msg + " failed, error " + u_errorName(status)); } else { errln(UnicodeString("FAIL: ") + msg + " failed, error " + u_errorName(status)); } return TRUE; } return FALSE; } uint32_t NumberFormatRoundTripTest::randLong() { // Assume 8-bit (or larger) rand values. Also assume // that the system rand() function is very poor, which it always is. uint32_t d; uint32_t i; char* poke = (char*)&d; for (i=0; i < sizeof(uint32_t); ++i) { poke[i] = (char)(rand() & 0xFF); } return d; } /** * Return a random value from -range..+range. */ double NumberFormatRoundTripTest::randomDouble(double range) { double a = randFraction(); return (2.0 * range * a) - range; } void NumberFormatRoundTripTest::start() { // test(NumberFormat.getInstance(new Locale("sr", "", ""))); UErrorCode status = U_ZERO_ERROR; NumberFormat *fmt = NULL; logln("Default Locale"); fmt = NumberFormat::createInstance(status); if (!failure(status, "NumberFormat::createInstance", TRUE)){ test(fmt); } delete fmt; fmt = NumberFormat::createCurrencyInstance(status); if (!failure(status, "NumberFormat::createCurrencyInstance", TRUE)){ test(fmt); } delete fmt; fmt = NumberFormat::createPercentInstance(status); if (!failure(status, "NumberFormat::createPercentInstance", TRUE)){ test(fmt); } delete fmt; int32_t locCount = 0; const Locale *loc = NumberFormat::getAvailableLocales(locCount); if(quick) { if(locCount > 5) locCount = 5; logln("Quick mode: only testing first 5 Locales"); } for(int i = 0; i < locCount; ++i) { UnicodeString name; logln(loc[i].getDisplayName(name)); fmt = NumberFormat::createInstance(loc[i], status); failure(status, "NumberFormat::createInstance"); test(fmt); delete fmt; fmt = NumberFormat::createCurrencyInstance(loc[i], status); failure(status, "NumberFormat::createCurrencyInstance"); test(fmt); delete fmt; fmt = NumberFormat::createPercentInstance(loc[i], status); failure(status, "NumberFormat::createPercentInstance"); test(fmt); delete fmt; } logln(UnicodeString("Numeric error ") + min_numeric_error + " to " + max_numeric_error); } void NumberFormatRoundTripTest::test(NumberFormat *fmt) { #if IEEE_754 && U_PLATFORM != U_PF_OS400 test(fmt, uprv_getNaN()); test(fmt, uprv_getInfinity()); test(fmt, -uprv_getInfinity()); #endif test(fmt, (int32_t)500); test(fmt, (int32_t)0); test(fmt, (int32_t)-0); test(fmt, 0.0); double negZero = 0.0; negZero /= -1.0; test(fmt, negZero); test(fmt, 9223372036854775808.0); test(fmt, -9223372036854775809.0); for(int i = 0; i < 10; ++i) { test(fmt, randomDouble(1)); test(fmt, randomDouble(10000)); test(fmt, uprv_floor((randomDouble(10000)))); test(fmt, randomDouble(1e50)); test(fmt, randomDouble(1e-50)); #if !(U_PF_OS390 <= U_PLATFORM && U_PLATFORM <= U_PF_OS400) test(fmt, randomDouble(1e100)); #elif IEEE_754 test(fmt, randomDouble(1e75)); #endif /* OS390 and OS400 */ // {sfb} When formatting with a percent instance, numbers very close to // DBL_MAX will fail the round trip. This is because: // 1) Format the double into a string --> INF% (since 100 * double > DBL_MAX) // 2) Parse the string into a double --> INF // 3) Re-format the double --> INF% // 4) The strings are equal, so that works. // 5) Calculate the proportional error --> INF, so the test will fail // I'll get around this by dividing by the multiplier to make sure // the double will stay in range. //if(fmt->getMultipler() == 1) DecimalFormat *df = dynamic_cast(fmt); if(df != NULL) { #if !(U_PF_OS390 <= U_PLATFORM && U_PLATFORM <= U_PF_OS400) /* DBL_MAX/2 is here because randomDouble does a *2 in the math */ test(fmt, randomDouble(DBL_MAX/2.0) / df->getMultiplier()); #elif IEEE_754 test(fmt, randomDouble(1e75) / df->getMultiplier()); #else test(fmt, randomDouble(1e65) / df->getMultiplier()); #endif } #if (defined(_MSC_VER) && _MSC_VER < 1400) || defined(__alpha__) || defined(U_OSF) // These machines and compilers don't fully support denormalized doubles, test(fmt, randomDouble(1e-292)); test(fmt, randomDouble(1e-100)); #elif U_PF_OS390 <= U_PLATFORM && U_PLATFORM <= U_PF_OS400 // i5/OS (OS/400) throws exceptions on denormalized numbers # if IEEE_754 test(fmt, randomDouble(1e-78)); test(fmt, randomDouble(1e-78)); // #else we're using something like the old z/OS floating point. # endif #else // This is a normal machine that can support IEEE754 denormalized doubles without throwing an error. test(fmt, randomDouble(DBL_MIN)); /* Usually 2.2250738585072014e-308 */ test(fmt, randomDouble(1e-100)); #endif } } void NumberFormatRoundTripTest::test(NumberFormat *fmt, double value) { test(fmt, Formattable(value)); } void NumberFormatRoundTripTest::test(NumberFormat *fmt, int32_t value) { test(fmt, Formattable(value)); } void NumberFormatRoundTripTest::test(NumberFormat *fmt, const Formattable& value) { fmt->setMaximumFractionDigits(999); DecimalFormat *df = dynamic_cast(fmt); if(df != NULL) { df->setRoundingIncrement(0.0); } UErrorCode status = U_ZERO_ERROR; UnicodeString s, s2, temp; if(isDouble(value)) s = fmt->format(value.getDouble(), s); else s = fmt->format(value.getLong(), s); Formattable n; UBool show = verbose; if(DEBUG_VAR) logln(/*value.getString(temp) +*/ " F> " + escape(s)); fmt->parse(s, n, status); if(U_FAILURE(status)) { UErrorCode infoStatus = U_ZERO_ERROR; const char* localeID = fmt->getLocaleID(ULOC_ACTUAL_LOCALE, infoStatus); localeID = (U_SUCCESS(infoStatus) && localeID)? localeID: "?"; errln(UnicodeString("FAIL: fmt->parse failed, locale: ") + localeID + ", error: " + u_errorName(status)); } if(DEBUG_VAR) logln(escape(s) + " P> " /*+ n.getString(temp)*/); if(isDouble(n)) s2 = fmt->format(n.getDouble(), s2); else s2 = fmt->format(n.getLong(), s2); if(DEBUG_VAR) logln(/*n.getString(temp) +*/ " F> " + escape(s2)); if(STRING_COMPARE) { if (s != s2) { errln("*** STRING ERROR \"" + escape(s) + "\" != \"" + escape(s2) + "\""); show = TRUE; } } if(EXACT_NUMERIC_COMPARE) { if(value != n) { errln("*** NUMERIC ERROR"); show = TRUE; } } else { // Compute proportional error double error = proportionalError(value, n); if(error > MAX_ERROR) { errln(UnicodeString("*** NUMERIC ERROR ") + error); show = TRUE; } if (error > max_numeric_error) max_numeric_error = error; if (error < min_numeric_error) min_numeric_error = error; } if (show) { errln(/*value.getString(temp) +*/ typeOf(value, temp) + " F> " + escape(s) + " P> " + (n.getType() == Formattable::kDouble ? n.getDouble() : (double)n.getLong()) /*n.getString(temp) */ + typeOf(n, temp) + " F> " + escape(s2)); } } double NumberFormatRoundTripTest::proportionalError(const Formattable& a, const Formattable& b) { double aa,bb; if(isDouble(a)) aa = a.getDouble(); else aa = a.getLong(); if(isDouble(b)) bb = b.getDouble(); else bb = b.getLong(); double error = aa - bb; if(aa != 0 && bb != 0) error /= aa; return uprv_fabs(error); } UnicodeString& NumberFormatRoundTripTest::typeOf(const Formattable& n, UnicodeString& result) { if(n.getType() == Formattable::kLong) { result = UnicodeString(" Long"); } else if(n.getType() == Formattable::kDouble) { result = UnicodeString(" Double"); } else if(n.getType() == Formattable::kString) { result = UnicodeString(" UnicodeString"); UnicodeString temp; } return result; } UnicodeString& NumberFormatRoundTripTest::escape(UnicodeString& s) { UnicodeString copy(s); s.remove(); for(int i = 0; i < copy.length(); ++i) { UChar32 c = copy.char32At(i); if (c >= 0x10000) { ++i; } if(c < 0x00FF) { s += c; } else { s += "+U"; char temp[16]; sprintf(temp, "%4X", c); // might not work s += temp; } } return s; } #endif /* #if !UCONFIG_NO_FORMATTING */