Merge pull request #399 from TsudaKageyu/float-conv

Added float conversion functions to ByteVector.
This commit is contained in:
Lukáš Lalinský 2014-07-22 12:10:54 +02:00
commit ee2908a6cf
10 changed files with 397 additions and 60 deletions

View File

@ -6,6 +6,8 @@ if(NOT ${CMAKE_VERSION} VERSION_LESS 2.8.12)
cmake_policy(SET CMP0022 OLD)
endif()
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules)
option(ENABLE_STATIC "Make static version of libtag" OFF)
if(ENABLE_STATIC)
add_definitions(-DTAGLIB_STATIC)

View File

@ -6,8 +6,9 @@ include(CheckLibraryExists)
include(CheckTypeSize)
include(CheckCXXSourceCompiles)
include(TestBigEndian)
include(TestFloatFormat)
# Check if the size of integral types are suitable.
# Check if the size of numeric types are suitable.
check_type_size("short" SIZEOF_SHORT)
if(NOT ${SIZEOF_SHORT} EQUAL 2)
@ -29,6 +30,16 @@ if(${SIZEOF_WCHAR_T} LESS 2)
MESSAGE(FATAL_ERROR "TagLib requires that wchar_t is sufficient to store a UTF-16 char.")
endif()
check_type_size("float" SIZEOF_FLOAT)
if(NOT ${SIZEOF_FLOAT} EQUAL 4)
MESSAGE(FATAL_ERROR "TagLib requires that float is 32-bit wide.")
endif()
check_type_size("double" SIZEOF_DOUBLE)
if(NOT ${SIZEOF_DOUBLE} EQUAL 8)
MESSAGE(FATAL_ERROR "TagLib requires that double is 64-bit wide.")
endif()
# Determine the CPU byte order.
test_big_endian(IS_BIG_ENDIAN)
@ -39,6 +50,18 @@ else()
set(SYSTEM_BYTEORDER 2)
endif()
# Check if the format of floating point types are suitable.
test_float_format(FP_IEEE754)
if(${FP_IEEE754} EQUAL 1)
set(FLOAT_BYTEORDER 1)
elseif(${FP_IEEE754} EQUAL 2)
set(FLOAT_BYTEORDER 2)
else()
MESSAGE(FATAL_ERROR "TagLib requires that floating point types are IEEE754 compliant.")
endif()
# Determine which kind of atomic operations your compiler supports.
check_cxx_source_compiles("

13
cmake/TestFloatFormat.c Normal file
View File

@ -0,0 +1,13 @@
int main()
{
double bin1[] = {
// "*TAGLIB*" encoded as a little-endian floating-point number
(double)3.9865557444897601e-105, (double)0.0
};
float bin2[] = {
// "*TL*" encoded as a little-endian floating-point number
(float)1.81480400e-013, (float)0.0
};
return 0;
}

View File

@ -0,0 +1,60 @@
# Returns 1 if IEEE754 little-endian, 2 if IEEE754 big-endian, otherwise 0.
MACRO(TEST_FLOAT_FORMAT FP_IEEE754)
IF(NOT FP_IEEE754)
TRY_COMPILE(HAVE_${FP_IEEE754} "${CMAKE_BINARY_DIR}" "${CMAKE_SOURCE_DIR}/cmake/TestFloatFormat.c"
COPY_FILE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin")
SET(FP_IEEE754 0)
IF(HAVE_${FP_IEEE754})
# dont match first/last letter because of string rounding errors :-)
FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin"
DOUBLE_IEEE754_LE LIMIT_COUNT 1 REGEX "TAGLIB")
FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin"
DOUBLE_IEEE754_BE LIMIT_COUNT 1 REGEX "BILGAT")
FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin"
FLOAT_IEEE754_LE LIMIT_COUNT 1 REGEX "TL")
FILE(STRINGS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/TestFloatFormat.bin"
FLOAT_IEEE754_BE LIMIT_COUNT 1 REGEX "LT")
IF(DOUBLE_IEEE754_LE AND FLOAT_IEEE754_LE)
SET(FP_IEEE754_LE 1)
ENDIF()
IF(DOUBLE_IEEE754_BE AND FLOAT_IEEE754_BE)
SET(FP_IEEE754_BE 1)
ENDIF()
# OS X Universal binaries will contain both strings, set it to the host
IF(FP_IEEE754_LE AND FP_IEEE754_BE)
IF(CMAKE_SYSTEM_PROCESSOR MATCHES powerpc)
SET(FP_IEEE754_LE FALSE)
SET(FP_IEEE754_BE TRUE)
ELSE()
SET(FP_IEEE754_LE TRUE)
SET(FP_IEEE754_BE FALSE)
ENDIF()
ENDIF()
IF(FP_IEEE754_LE)
SET(FP_IEEE754 1)
ELSEIF(FP_IEEE754_BE)
SET(FP_IEEE754 2)
ENDIF()
ENDIF()
# just some informational output for the user
IF(FP_IEEE754_LE)
MESSAGE(STATUS "Checking the floating point format - IEEE754 (LittleEndian)")
ELSEIF(FP_IEEE754_BE)
MESSAGE(STATUS "Checking the floating point format - IEEE754 (BigEndian)")
ELSE()
MESSAGE(STATUS "Checking the floating point format - Not IEEE754 or failed to detect.")
ENDIF()
SET(FP_IEEE754 "${${FP_IEEE754}}" CACHE INTERNAL "Result of TEST_FLOAT_FORMAT" FORCE)
ENDIF()
ENDMACRO(TEST_FLOAT_FORMAT FP_IEEE754)

View File

@ -1,9 +1,13 @@
/* config.h. Generated by cmake from config.h.cmake */
/* Indicates the byte order of your target system */
/* Integer byte order of your target system */
/* 1 if little-endian, 2 if big-endian. */
#cmakedefine SYSTEM_BYTEORDER ${SYSTEM_BYTEORDER}
/* IEEE754 byte order of your target system. */
/* 1 if little-endian, 2 if big-endian. */
#cmakedefine FLOAT_BYTEORDER ${FLOAT_BYTEORDER}
/* Defined if your compiler supports some byte swap functions */
#cmakedefine HAVE_GCC_BYTESWAP_16 1
#cmakedefine HAVE_GCC_BYTESWAP_32 1

View File

@ -25,56 +25,8 @@
#include <tstring.h>
#include <tdebug.h>
#include <cmath>
// ldexp is a c99 function, which might not be defined in <cmath>
// so we pull in math.h too and hope it does the right (wrong) thing
// wrt. c99 functions in C++
#include <math.h>
#include "aiffproperties.h"
////////////////////////////////////////////////////////////////////////////////
// nasty 80-bit float helpers
////////////////////////////////////////////////////////////////////////////////
#define UnsignedToFloat(u) (((double)((long)(u - 2147483647L - 1))) + 2147483648.0)
static double ConvertFromIeeeExtended(const TagLib::uchar *bytes)
{
double f;
int expon;
unsigned long hiMant, loMant;
expon = ((bytes[0] & 0x7F) << 8) | (bytes[1] & 0xFF);
hiMant = ((unsigned long)(bytes[2] & 0xFF) << 24) |
((unsigned long)(bytes[3] & 0xFF) << 16) |
((unsigned long)(bytes[4] & 0xFF) << 8) |
((unsigned long)(bytes[5] & 0xFF));
loMant = ((unsigned long)(bytes[6] & 0xFF) << 24) |
((unsigned long)(bytes[7] & 0xFF) << 16) |
((unsigned long)(bytes[8] & 0xFF) << 8) |
((unsigned long)(bytes[9] & 0xFF));
if (expon == 0 && hiMant == 0 && loMant == 0)
f = 0;
else {
if(expon == 0x7FFF) /* Infinity or NaN */
f = HUGE_VAL;
else {
expon -= 16383;
f = ldexp(UnsignedToFloat(hiMant), expon -= 31);
f += ldexp(UnsignedToFloat(loMant), expon -= 32);
}
}
if(bytes[0] & 0x80)
return -f;
else
return f;
}
using namespace TagLib;
class RIFF::AIFF::Properties::PropertiesPrivate
@ -153,7 +105,7 @@ void RIFF::AIFF::Properties::read(const ByteVector &data)
d->channels = data.toShort(0U);
d->sampleFrames = data.toUInt(2U);
d->sampleWidth = data.toShort(6U);
double sampleRate = ConvertFromIeeeExtended(reinterpret_cast<const uchar *>(data.data() + 8));
const long double sampleRate = data.toFloat80BE(8);
d->sampleRate = (int)sampleRate;
d->bitrate = (int)((sampleRate * d->sampleWidth * d->channels) / 1000.0);
d->length = d->sampleRate > 0 ? d->sampleFrames / d->sampleRate : 0;

View File

@ -29,6 +29,8 @@
#include <algorithm>
#include <iostream>
#include <limits>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <cstddef>
@ -238,6 +240,95 @@ ByteVector fromNumber(T value, bool mostSignificantByteFirst)
return ByteVector(reinterpret_cast<const char *>(&value), sizeof(T));
}
template <typename TFloat, typename TInt, Utils::ByteOrder ENDIAN>
TFloat toFloat(const ByteVector &v, size_t offset)
{
if (offset > v.size() - sizeof(TInt)) {
debug("toFloat() - offset is out of range. Returning 0.");
return 0.0;
}
union {
TInt i;
TFloat f;
} tmp;
::memcpy(&tmp, v.data() + offset, sizeof(TInt));
if(ENDIAN != Utils::FloatByteOrder)
tmp.i = Utils::byteSwap(tmp.i);
return tmp.f;
}
template <typename TFloat, typename TInt, Utils::ByteOrder ENDIAN>
ByteVector fromFloat(TFloat value)
{
union {
TInt i;
TFloat f;
} tmp;
tmp.f = value;
if(ENDIAN != Utils::FloatByteOrder)
tmp.i = Utils::byteSwap(tmp.i);
return ByteVector(reinterpret_cast<char *>(&tmp), sizeof(TInt));
}
template <Utils::ByteOrder ENDIAN>
long double toFloat80(const ByteVector &v, size_t offset)
{
if(offset > v.size() - 10) {
debug("toFloat80() - offset is out of range. Returning 0.");
return 0.0;
}
uchar bytes[10];
::memcpy(bytes, v.data() + offset, 10);
if(ENDIAN == Utils::LittleEndian) {
std::swap(bytes[0], bytes[9]);
std::swap(bytes[1], bytes[8]);
std::swap(bytes[2], bytes[7]);
std::swap(bytes[3], bytes[6]);
std::swap(bytes[4], bytes[5]);
}
// 1-bit sign
const bool negative = ((bytes[0] & 0x80) != 0);
// 15-bit exponent
const int exponent = ((bytes[0] & 0x7F) << 8) | bytes[1];
// 64-bit fraction. Leading 1 is explicit.
const ulonglong fraction
= (static_cast<ulonglong>(bytes[2]) << 56)
| (static_cast<ulonglong>(bytes[3]) << 48)
| (static_cast<ulonglong>(bytes[4]) << 40)
| (static_cast<ulonglong>(bytes[5]) << 32)
| (static_cast<ulonglong>(bytes[6]) << 24)
| (static_cast<ulonglong>(bytes[7]) << 16)
| (static_cast<ulonglong>(bytes[8]) << 8)
| (static_cast<ulonglong>(bytes[9]));
long double val;
if(exponent == 0 && fraction == 0)
val = 0;
else {
if(exponent == 0x7FFF) {
debug("toFloat80() - can't handle the infinity or NaN. Returning 0.");
return 0.0;
}
else
val = ::ldexp(static_cast<long double>(fraction), exponent - 16383 - 63);
}
if(negative)
return -val;
else
return val;
}
class DataPrivate : public RefCounter
{
public:
@ -371,6 +462,26 @@ ByteVector ByteVector::fromLongLong(long long value, bool mostSignificantByteFir
return fromNumber<unsigned long long>(value, mostSignificantByteFirst);
}
ByteVector ByteVector::fromFloat32LE(float value)
{
return fromFloat<float, uint, Utils::LittleEndian>(value);
}
ByteVector ByteVector::fromFloat32BE(float value)
{
return fromFloat<float, uint, Utils::BigEndian>(value);
}
ByteVector ByteVector::fromFloat64LE(double value)
{
return fromFloat<double, ulonglong, Utils::LittleEndian>(value);
}
ByteVector ByteVector::fromFloat64BE(double value)
{
return fromFloat<double, ulonglong, Utils::BigEndian>(value);
}
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
@ -708,6 +819,36 @@ long long ByteVector::toLongLong(uint offset, bool mostSignificantByteFirst) con
return toNumber<unsigned long long>(*this, offset, mostSignificantByteFirst);
}
float ByteVector::toFloat32LE(size_t offset) const
{
return toFloat<float, uint, Utils::LittleEndian>(*this, offset);
}
float ByteVector::toFloat32BE(size_t offset) const
{
return toFloat<float, uint, Utils::BigEndian>(*this, offset);
}
double ByteVector::toFloat64LE(size_t offset) const
{
return toFloat<double, ulonglong, Utils::LittleEndian>(*this, offset);
}
double ByteVector::toFloat64BE(size_t offset) const
{
return toFloat<double, ulonglong, Utils::BigEndian>(*this, offset);
}
long double ByteVector::toFloat80LE(size_t offset) const
{
return toFloat80<Utils::LittleEndian>(*this, offset);
}
long double ByteVector::toFloat80BE(size_t offset) const
{
return toFloat80<Utils::BigEndian>(*this, offset);
}
const char &ByteVector::operator[](int index) const
{
return d->data->data[d->offset + index];

View File

@ -291,7 +291,7 @@ namespace TagLib {
uint toUInt(bool mostSignificantByteFirst = true) const;
/*!
* Converts the 4 bytes at \a offset of the vector to an unsigned integer.
* Converts the 4 bytes at \a offset of the vector to an unsigned integer.
*
* If \a mostSignificantByteFirst is true this will operate left to right
* evaluating the integer. For example if \a mostSignificantByteFirst is
@ -303,8 +303,8 @@ namespace TagLib {
uint toUInt(uint offset, bool mostSignificantByteFirst = true) const;
/*!
* Converts the \a length bytes at \a offset of the vector to an unsigned
* integer. If \a length is larger than 4, the excess is ignored.
* Converts the \a length bytes at \a offset of the vector to an unsigned
* integer. If \a length is larger than 4, the excess is ignored.
*
* If \a mostSignificantByteFirst is true this will operate left to right
* evaluating the integer. For example if \a mostSignificantByteFirst is
@ -383,6 +383,46 @@ namespace TagLib {
*/
long long toLongLong(uint offset, bool mostSignificantByteFirst = true) const;
/*
* Converts the 4 bytes at \a offset of the vector to a float as an IEEE754
* 32-bit little-endian floating point number.
*/
float toFloat32LE(size_t offset) const;
/*
* Converts the 4 bytes at \a offset of the vector to a float as an IEEE754
* 32-bit big-endian floating point number.
*/
float toFloat32BE(size_t offset) const;
/*
* Converts the 8 bytes at \a offset of the vector to a double as an IEEE754
* 64-bit little-endian floating point number.
*/
double toFloat64LE(size_t offset) const;
/*
* Converts the 8 bytes at \a offset of the vector to a double as an IEEE754
* 64-bit big-endian floating point number.
*/
double toFloat64BE(size_t offset) const;
/*
* Converts the 10 bytes at \a offset of the vector to a long double as an
* IEEE754 80-bit little-endian floating point number.
*
* \note This may compromise the precision depends on the size of long double.
*/
long double toFloat80LE(size_t offset) const;
/*
* Converts the 10 bytes at \a offset of the vector to a long double as an
* IEEE754 80-bit big-endian floating point number.
*
* \note This may compromise the precision depends on the size of long double.
*/
long double toFloat80BE(size_t offset) const;
/*!
* Creates a 4 byte ByteVector based on \a value. If
* \a mostSignificantByteFirst is true, then this will operate left to right
@ -415,6 +455,38 @@ namespace TagLib {
*/
static ByteVector fromLongLong(long long value, bool mostSignificantByteFirst = true);
/*!
* Creates a 4 byte ByteVector based on \a value as an IEEE754 32-bit
* little-endian floating point number.
*
* \see fromFloat32BE()
*/
static ByteVector fromFloat32LE(float value);
/*!
* Creates a 4 byte ByteVector based on \a value as an IEEE754 32-bit
* big-endian floating point number.
*
* \see fromFloat32LE()
*/
static ByteVector fromFloat32BE(float value);
/*!
* Creates a 8 byte ByteVector based on \a value as an IEEE754 64-bit
* little-endian floating point number.
*
* \see fromFloat64BE()
*/
static ByteVector fromFloat64LE(double value);
/*!
* Creates a 8 byte ByteVector based on \a value as an IEEE754 64-bit
* big-endian floating point number.
*
* \see fromFloat64LE()
*/
static ByteVector fromFloat64BE(double value);
/*!
* Returns a ByteVector based on the CString \a s.
*/

View File

@ -155,11 +155,11 @@ namespace TagLib
# if SYSTEM_BYTEORDER == 1
const ByteOrder SystemByteOrder = LittleEndian;
const ByteOrder SystemByteOrder = LittleEndian;
# else
const ByteOrder SystemByteOrder = BigEndian;
const ByteOrder SystemByteOrder = BigEndian;
# endif
@ -178,8 +178,40 @@ namespace TagLib
else
return BigEndian;
}
const ByteOrder SystemByteOrder = systemByteOrder();
const ByteOrder SystemByteOrder = systemByteOrder();
#endif
#ifdef FLOAT_BYTEORDER
# if FLOAT_BYTEORDER == 1
const ByteOrder FloatByteOrder = LittleEndian;
# else
const ByteOrder FloatByteOrder = BigEndian;
# endif
#else
inline ByteOrder floatByteOrder()
{
double bin[] = {
// "*TAGLIB*" encoded as a little-endian floating-point number
(double) 3.9865557444897601e-105, (double) 0.0
};
char *str = (char*)&bin[0];
if(strncmp(&str[1], "TAGLIB", 6) == 0)
return LittleEndian;
else
return BigEndian;
}
const ByteOrder FloatByteOrder = floatByteOrder();
#endif
}

View File

@ -22,6 +22,7 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <cstring>
#include <tbytevector.h>
#include <tbytevectorlist.h>
#include <cppunit/extensions/HelperMacros.h>
@ -38,7 +39,7 @@ class TestByteVector : public CppUnit::TestFixture
CPPUNIT_TEST(testRfind1);
CPPUNIT_TEST(testRfind2);
CPPUNIT_TEST(testToHex);
CPPUNIT_TEST(testToUShort);
CPPUNIT_TEST(testNumericCoversion);
CPPUNIT_TEST(testReplace);
CPPUNIT_TEST_SUITE_END();
@ -194,13 +195,50 @@ public:
CPPUNIT_ASSERT_EQUAL(ByteVector("f0e1d2c3b4a5968778695a4b3c2d1e0f"), v.toHex());
}
void testToUShort()
void testNumericCoversion()
{
CPPUNIT_ASSERT_EQUAL((unsigned short)0xFFFF, ByteVector("\xff\xff", 2).toUShort());
CPPUNIT_ASSERT_EQUAL((unsigned short)0x0001, ByteVector("\x00\x01", 2).toUShort());
CPPUNIT_ASSERT_EQUAL((unsigned short)0x0100, ByteVector("\x00\x01", 2).toUShort(false));
CPPUNIT_ASSERT_EQUAL((unsigned short)0xFF01, ByteVector("\xFF\x01", 2).toUShort());
CPPUNIT_ASSERT_EQUAL((unsigned short)0x01FF, ByteVector("\xFF\x01", 2).toUShort(false));
const uchar PI32LE[] = { 0x00, 0xdb, 0x0f, 0x49, 0x40 };
const uchar PI32BE[] = { 0x00, 0x40, 0x49, 0x0f, 0xdb };
const uchar PI64LE[] = { 0x00, 0x18, 0x2d, 0x44, 0x54, 0xfb, 0x21, 0x09, 0x40 };
const uchar PI64BE[] = { 0x00, 0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18 };
const uchar PI80LE[] = { 0x00, 0x00, 0xc0, 0x68, 0x21, 0xa2, 0xda, 0x0f, 0xc9, 0x00, 0x40 };
const uchar PI80BE[] = { 0x00, 0x40, 0x00, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc0, 0x00 };
ByteVector pi32le(reinterpret_cast<const char*>(PI32LE), 5);
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi32le.toFloat32LE(1) * 10000));
ByteVector pi32be(reinterpret_cast<const char*>(PI32BE), 5);
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi32be.toFloat32BE(1) * 10000));
ByteVector pi64le(reinterpret_cast<const char*>(PI64LE), 9);
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi64le.toFloat64LE(1) * 10000));
ByteVector pi64be(reinterpret_cast<const char*>(PI64BE), 9);
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi64be.toFloat64BE(1) * 10000));
ByteVector pi80le(reinterpret_cast<const char*>(PI80LE), 11);
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi80le.toFloat80LE(1) * 10000));
ByteVector pi80be(reinterpret_cast<const char*>(PI80BE), 11);
CPPUNIT_ASSERT_EQUAL(31415, static_cast<int>(pi80be.toFloat80BE(1) * 10000));
ByteVector pi32le2 = ByteVector::fromFloat32LE(pi32le.toFloat32LE(1));
CPPUNIT_ASSERT(memcmp(pi32le.data() + 1, pi32le2.data(), 4) == 0);
ByteVector pi32be2 = ByteVector::fromFloat32BE(pi32be.toFloat32BE(1));
CPPUNIT_ASSERT(memcmp(pi32be.data() + 1, pi32be2.data(), 4) == 0);
ByteVector pi64le2 = ByteVector::fromFloat64LE(pi64le.toFloat64LE(1));
CPPUNIT_ASSERT(memcmp(pi64le.data() + 1, pi64le2.data(), 8) == 0);
ByteVector pi64be2 = ByteVector::fromFloat64BE(pi64be.toFloat64BE(1));
CPPUNIT_ASSERT(memcmp(pi64be.data() + 1, pi64be2.data(), 8) == 0);
}
void testReplace()