From bac174ebfc16c5f1201792b28db288576b518dbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Thu, 13 Oct 2016 18:10:29 +0200 Subject: [PATCH] [poincare] In float, add method to convert float to text Change-Id: I9a85c02e0b2843ee999847b821b9d24f1452eb5d --- poincare/Makefile | 5 +- poincare/include/poincare/float.h | 39 +++++++++---- poincare/src/float.cpp | 92 +++++++++++++++++++++++++++++++ poincare/test/float.cpp | 26 +++++++++ 4 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 poincare/test/float.cpp diff --git a/poincare/Makefile b/poincare/Makefile index 0501ae727..b26bcf590 100644 --- a/poincare/Makefile +++ b/poincare/Makefile @@ -34,15 +34,16 @@ objs += $(addprefix poincare/src/layout/,\ tests += $(addprefix poincare/test/,\ addition.cpp\ + float.cpp\ fraction.cpp\ - integer.cpp\ identity.cpp\ + integer.cpp\ product.cpp\ simplify_utils.cpp\ simplify_addition.cpp\ simplify_product.cpp\ - trigo.cpp\ subtraction.cpp\ + trigo.cpp\ ) ifdef POINCARE_TESTS_PRINT_EXPRESSIONS diff --git a/poincare/include/poincare/float.h b/poincare/include/poincare/float.h index d0998e709..ecf0af886 100644 --- a/poincare/include/poincare/float.h +++ b/poincare/include/poincare/float.h @@ -4,17 +4,36 @@ #include class Float : public LeafExpression { - public: - Float(float f); - ~Float(); +public: + Float(float f); + ~Float(); - ExpressionLayout * createLayout() const override; - float approximate(Context& context) const override; - Type type() const override; - Expression * clone() const override; - bool valueEquals(const Expression * e) const override; - private: - float m_float; + ExpressionLayout * createLayout() const override; + float approximate(Context& context) const override; + Type type() const override; + Expression * clone() const override; + bool valueEquals(const Expression * e) const override; + + enum class DisplayMode { + Scientific, + Decimal + }; + + /* The parameter 'DisplayMode' refers to the way to display float 'scientific' or + * 'decimal'. The code only handles 'scientific' so far. */ + void convertFloatToText(char * buffer, int bufferSize, int numberOfDigitsInMantissa, DisplayMode mode = DisplayMode::Scientific); + +private: + /* This function prints the int i in the buffer with a '.' at the position + * specified by the decimalMarkerPosition. It starts printing at the end of the + * buffer and print from right to left. The integer given should be of the right + * length to be written in bufferLength chars. If the integer is to small, the + * empty chars on the left side are completed with '0'. If the integer is too + * big, the printing stops when no more empty chars are available without + * returning any warning. */ + void static printBase10IntegerWithDecimalMarker(char * buffer, int bufferSize, int i, int decimalMarkerPosition); + + float m_float; }; #endif diff --git a/poincare/src/float.cpp b/poincare/src/float.cpp index 0f30333c8..79ac0f55f 100644 --- a/poincare/src/float.cpp +++ b/poincare/src/float.cpp @@ -2,6 +2,7 @@ extern "C" { #include #include #include +#include } #include @@ -33,3 +34,94 @@ bool Float::valueEquals(const Expression * e) const { assert(e->type() == Expression::Type::Float); return m_float == ((Float *)e)->m_float; } + +void Float::printBase10IntegerWithDecimalMarker(char * buffer, int bufferSize, int i, int decimalMarkerPosition) { + /* The decimal marker position is always preceded by a char, thus, it is never + * in first position. When called by convertFloatToText, the buffer length is + * always > 0 as we asserted a minimal number of available chars. */ + assert(bufferSize > 0 && decimalMarkerPosition != 0); + int endChar = bufferSize - 1, startChar = 0; + int dividend = i, digit = 0, quotien = 0; + if (i < 0) { + buffer[startChar++] = '-'; + dividend = -i; + decimalMarkerPosition += 1; + } + /* This loop acts correctly as we asserted the endChar >= 0 and + * decimalMarkerPosition != 0 */ + do { + if (endChar == decimalMarkerPosition) { + buffer[endChar--] = '.'; + } + quotien = dividend/10; + digit = dividend - quotien*10; + buffer[endChar--] = '0'+digit; + dividend = quotien; + } while (endChar >= startChar); +} + +void Float::convertFloatToText(char * buffer, int bufferSize, + int numberOfDigitsInMantissa, DisplayMode mode) { + /* We here assert that the buffer is long enough to display with the right + * number of digits in the mantissa. If numberOfDigitsInMantissa = 7, the + * worst case has the form -1.999999e-38 (7+6+1 char). */ + assert(bufferSize > 6 + numberOfDigitsInMantissa); + + // TO DO: if (isinf(m_float)) { + float maximalFloat = 3.4f*powf(10, 38); + if (m_float > maximalFloat || m_float < -maximalFloat) { + buffer[0] = 'N'; + buffer[1] = 'a'; + buffer[2] = 'N'; + buffer[3] = 0; + return; + } + + float logBase10 = m_float != 0.0f ? log10f(fabsf(m_float)) : 0; + int exponentInBase10 = logBase10; + if ((int)m_float == 0 and logBase10 != exponentInBase10) { + /* For floats < 0, the exponent in base 10 is the inferior integer part of + * log10(float). We thus decrement the exponent for float < 0 whose exponent + * is not an integer. */ + exponentInBase10--; + } + + int numberOfCharExponent = exponentInBase10 != 0 ? log10f(fabsf((float)exponentInBase10)) + 1 : 1; + if (exponentInBase10 < 0){ + // If the exponent is < 0, we need a additional char for the sign + numberOfCharExponent++; + } + + /* Future optimisation, if mode > 0, find the position of decimalMarker and + * decide whether to display the exponent. */ + + // Number of char available for the mantissa + int availableCharsForMantissaWithoutSign = numberOfDigitsInMantissa + 1; + int availableCharsForMantissaWithSign = m_float >= 0 ? availableCharsForMantissaWithoutSign : availableCharsForMantissaWithoutSign + 1; + + // Compute mantissa + /* The number of digits in an integer is capped because the maximal integer is + * 2^31 - 1. As our mantissa is an integer, we assert that we stay beyond this + * threshold during computation. */ + int numberMaximalOfCharsInInteger = log10f(powf(2, 31)); + assert(availableCharsForMantissaWithoutSign - 1 < numberMaximalOfCharsInInteger); + int mantissa = m_float * powf(10, availableCharsForMantissaWithoutSign - exponentInBase10 - 2); + + // Supress the 0 on the right side of the mantissa + int dividend = fabsf((float)mantissa); + int quotien = dividend/10; + int digit = dividend - quotien*10; + while (digit == 0 && availableCharsForMantissaWithSign > 2) { + mantissa = mantissa/10; + availableCharsForMantissaWithSign--; + dividend = quotien; + quotien = dividend/10; + digit = dividend - quotien*10; + } + + // Print sequentially mantissa and exponent + printBase10IntegerWithDecimalMarker(buffer, availableCharsForMantissaWithSign, mantissa, 1); + buffer[availableCharsForMantissaWithSign] = 'e'; + printBase10IntegerWithDecimalMarker(buffer+availableCharsForMantissaWithSign+1, numberOfCharExponent, exponentInBase10, -1); + buffer[availableCharsForMantissaWithSign+1+numberOfCharExponent] = 0; +} diff --git a/poincare/test/float.cpp b/poincare/test/float.cpp new file mode 100644 index 000000000..95a4144f2 --- /dev/null +++ b/poincare/test/float.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +QUIZ_CASE(poincare_float_to_text) { + char buffer [14]; + Float(123.456).convertFloatToText(buffer, 14, 7); + assert(strcmp(buffer, "1.23456e2") == 0); + Float(1.234567891011).convertFloatToText(buffer, 14, 7); + assert(strcmp(buffer, "1.234567e0") == 0); + Float(2).convertFloatToText(buffer, 14, 7); + assert(strcmp(buffer, "2.e0") == 0); + Float(123456789).convertFloatToText(buffer, 14, 7); + assert(strcmp(buffer, "1.234567e8") == 0); + Float(0.00000123456789).convertFloatToText(buffer, 14, 7); + assert(strcmp(buffer, "1.234567e-6") == 0); + Float(0.99).convertFloatToText(buffer, 14, 7); + assert(strcmp(buffer, "9.9e-1") == 0); + Float(-123.456789).convertFloatToText(buffer, 14, 7); + assert(strcmp(buffer, "-1.234567e2") == 0); + Float(-0.000123456789).convertFloatToText(buffer, 14, 7); + assert(strcmp(buffer, "-1.234567e-4") == 0); + Float(0).convertFloatToText(buffer, 14, 7); + assert(strcmp(buffer, "0.e0") == 0); +}