blob: f2a155398b2a3a5ab821692b9760a2589d6218f8 [file] [log] [blame]
 /* * Copyright 2017 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkFloatToDecimal.h" #include #include #include #include "SkTypes.h" // returns `value * pow(base, e)`, assuming `e` is positive. static double pow_by_squaring(double value, double base, int e) { // https://en.wikipedia.org/wiki/Exponentiation_by_squaring SkASSERT(e > 0); while (true) { if (e & 1) { value *= base; } e >>= 1; if (0 == e) { return value; } base *= base; } } // Return pow(10.0, e), optimized for common cases. static double pow10(int e) { switch (e) { case 0: return 1.0; // common cases case 1: return 10.0; case 2: return 100.0; case 3: return 1e+03; case 4: return 1e+04; case 5: return 1e+05; case 6: return 1e+06; case 7: return 1e+07; case 8: return 1e+08; case 9: return 1e+09; case 10: return 1e+10; case 11: return 1e+11; case 12: return 1e+12; case 13: return 1e+13; case 14: return 1e+14; case 15: return 1e+15; default: if (e > 15) { return pow_by_squaring(1e+15, 10.0, e - 15); } else { SkASSERT(e < 0); return pow_by_squaring(1.0, 0.1, -e); } } } /** Write a string into result, includeing a terminating '\0' (for unit testing). Return strlen(result) (for SkWStream::write) The resulting string will be in the form /[-]?([0-9]*.)?[0-9]+/ and sscanf(result, "%f", &x) will return the original value iff the value is finite. This function accepts all possible input values. Motivation: "PDF does not support [numbers] in exponential format (such as 6.02e23)." Otherwise, this function would rely on a sprintf-type function from the standard library. */ unsigned SkFloatToDecimal(float value, char result[kMaximumSkFloatToDecimalLength]) { /* The longest result is -FLT_MIN. We serialize it as "-.0000000000000000000000000000000000000117549435" which has 48 characters plus a terminating '\0'. */ static_assert(kMaximumSkFloatToDecimalLength == 49, ""); // 3 = '-', '.', and '\0' characters. // 9 = number of significant digits // abs(FLT_MIN_10_EXP) = number of zeros in FLT_MIN static_assert(kMaximumSkFloatToDecimalLength == 3 + 9 - FLT_MIN_10_EXP, ""); /* section C.1 of the PDF1.4 spec (http://goo.gl/0SCswJ) says that most PDF rasterizers will use fixed-point scalars that lack the dynamic range of floats. Even if this is the case, I want to serialize these (uncommon) very small and very large scalar values with enough precision to allow a floating-point rasterizer to read them in with perfect accuracy. Experimentally, rasterizers such as pdfium do seem to benefit from this. Rasterizers that rely on fixed-point scalars should gracefully ignore these values that they can not parse. */ char* output = &result[0]; const char* const end = &result[kMaximumSkFloatToDecimalLength - 1]; // subtract one to leave space for '\0'. /* This function is written to accept any possible input value, including non-finite values such as INF and NAN. In that case, we ignore value-correctness and and output a syntacticly-valid number. */ if (value == INFINITY) { value = FLT_MAX; // nearest finite float. } if (value == -INFINITY) { value = -FLT_MAX; // nearest finite float. } if (!std::isfinite(value) || value == 0.0f) { // NAN is unsupported in PDF. Always output a valid number. // Also catch zero here, as a special case. *output++ = '0'; *output = '\0'; return static_cast(output - result); } if (value < 0.0) { *output++ = '-'; value = -value; } SkASSERT(value >= 0.0f); int binaryExponent; (void)std::frexp(value, &binaryExponent); static const double kLog2 = 0.3010299956639812; // log10(2.0); int decimalExponent = static_cast(std::floor(kLog2 * binaryExponent)); int decimalShift = decimalExponent - 8; double power = pow10(-decimalShift); SkASSERT(value * power <= (double)INT_MAX); int d = static_cast(value * power + 0.5); // SkASSERT(value == (float)(d * pow(10.0, decimalShift))); SkASSERT(d <= 999999999); if (d > 167772159) { // floor(pow(10,1+log10(1<<24))) // need one fewer decimal digits for 24-bit precision. decimalShift = decimalExponent - 7; // SkASSERT(power * 0.1 = pow10(-decimalShift)); // recalculate to get rounding right. d = static_cast(value * (power * 0.1) + 0.5); SkASSERT(d <= 99999999); } while (d % 10 == 0) { d /= 10; ++decimalShift; } SkASSERT(d > 0); // SkASSERT(value == (float)(d * pow(10.0, decimalShift))); unsigned char buffer[9]; // decimal value buffer. int bufferIndex = 0; do { buffer[bufferIndex++] = d % 10; d /= 10; } while (d != 0); SkASSERT(bufferIndex <= (int)sizeof(buffer) && bufferIndex > 0); if (decimalShift >= 0) { do { --bufferIndex; *output++ = '0' + buffer[bufferIndex]; } while (bufferIndex); for (int i = 0; i < decimalShift; ++i) { *output++ = '0'; } } else { int placesBeforeDecimal = bufferIndex + decimalShift; if (placesBeforeDecimal > 0) { while (placesBeforeDecimal-- > 0) { --bufferIndex; *output++ = '0' + buffer[bufferIndex]; } *output++ = '.'; } else { *output++ = '.'; int placesAfterDecimal = -placesBeforeDecimal; while (placesAfterDecimal-- > 0) { *output++ = '0'; } } while (bufferIndex > 0) { --bufferIndex; *output++ = '0' + buffer[bufferIndex]; if (output == end) { break; // denormalized: don't need extra precision. // Note: denormalized numbers will not have the same number of // significantDigits, but do not need them to round-trip. } } } SkASSERT(output <= end); *output = '\0'; return static_cast(output - result); }