Created
August 9, 2021 20:24
-
-
Save michaeleisel/f7b6ece0587bf982895d1eb0bf2b2aa8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
void fallback(double d, char *buffer) { | |
snprintf(buffer, 25, "%0.16e", d); | |
} | |
void handleUncommonCases(double d, char *buffer) { | |
if (std::isinf(d)) { | |
if (d < 0) { | |
(*buffer++) = '-'; | |
} | |
strcpy(buffer, "Infinity"); | |
} else if (std::isnan(d)) { | |
strcpy(buffer, "NaN"); | |
} else if (!std::isnormal(d)) { | |
// This logic is platform-specific anyways, so let the platform handle it | |
sprintf(buffer, "%0.17e", d); | |
} | |
} | |
// Note that NaN and infinity are not allowed in JSON | |
// todo: add subnormal/infinity tests, zero test for all forms, e.g. negative | |
// todo: is 17 digits still sufficient when we're rounding down and not to nearest? | |
void new_mult_style(double d, char *buffer) { | |
// Decompose double | |
uint64_t bits = 0; | |
memcpy(&bits, &d, sizeof(d)); | |
uint64_t mantissa = (bits & ((~0ULL) >> 12)) | (1ULL << 52); | |
int exp = ((bits >> 52) & 0x7FF) - 1023; | |
// Handle uncommon cases | |
if (unlikely(exp == 1024 /* infinite or NaN */ || (exp == -1023 && mantissa != (1ULL << 52)/* subnormal */))) { | |
handleUncommonCases(d, buffer); | |
return; | |
} | |
// Handle if negative | |
bool isNegative = bits >> 63; | |
if (isNegative) { | |
buffer[0] = '-'; | |
buffer++; | |
} | |
// Compute product | |
PowerConversion conversion = kPowerConversions[exp]; | |
int16_t pow10 = conversion.power; | |
if (mantissa > conversion.mantissaCutoff) { | |
pow10--; | |
} | |
Pair pair = getPair(pow10); | |
int16_t power = pair.power; | |
power -= exp; | |
__uint128_t product128 = ((__uint128_t)pair.mantissa) * mantissa; | |
uint64_t product = (uint64_t)(product128 >> power); | |
if (unlikely(!(0 <= pow10 && pow10 < 26))) { | |
if (unlikely((product128 + mantissa) >> power != product)) { | |
fallback(d, buffer); | |
return; | |
} | |
} | |
// Write out digits | |
uint64_t first = product / 10000000000000000ULL; | |
product -= first * 10000000000000000ULL; | |
(*buffer++) = '0' + first; | |
(*buffer++) = '.'; | |
uint32_t lo8 = (uint32_t)(product % 100000000); | |
uint32_t hi8 = (uint32_t)(product / 100000000); | |
uint16_t lolo4 = lo8 % 10000; | |
uint16_t lohi4 = lo8 / 10000; | |
uint16_t hilo4 = hi8 % 10000; | |
uint16_t hihi4 = hi8 / 10000; | |
// Could gate each of these memcpys with an if statement, then finding the zero cutoff is easier | |
memcpy(buffer, kBigStrings[hihi4], 4); | |
memcpy(buffer + 4, kBigStrings[hilo4], 4); | |
memcpy(buffer + 8, kBigStrings[lohi4], 4); | |
memcpy(buffer + 12, kBigStrings[lolo4], 4); | |
// Remove trailing zeros | |
char *mantissaEnd = buffer - 1; // Remove '.' if unnecessary | |
for (int i = 15; i >= 0; i--) { | |
if (buffer[i] != '0') { | |
mantissaEnd = buffer + i + 1; | |
break; | |
} | |
} | |
buffer = mantissaEnd; // Just overwrite what we had | |
// Add exponent | |
int32_t target = -pow10 + 17 - 1 /* account for digit to left of decimal */; | |
if (target == 0) { | |
*buffer = '\0'; | |
return; | |
} | |
if (target < 0) { | |
(*buffer++) = '-'; | |
target = -target; | |
} | |
(*buffer++) = 'E'; | |
const char *string = kBigStrings[target]; | |
if (unlikely(target >= 100)) { | |
(*buffer++) = string[1]; | |
} | |
if (unlikely(target >= 10)) { | |
(*buffer++) = string[2]; | |
} | |
(*buffer++) = string[3]; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment