fp_uscale.c: replace fmt_fp.c and readfloat.c with uscale algorithm

Replace separate float formatting (fmt_fp.c) and parsing (readfloat.c)
implementations with a unified fp_uscale.c using 128-bit unrounded
scaling. Both mrb_format_float() and mrb_read_float() now share a
single pow10 table and uscale() primitive for decimal/binary conversion.

This fixes subnormal parsing accuracy (old code returned 0.0 for the
smallest subnormals) and corrects %.2f rounding for values like
12345.125. Table size grows from ~5KB to ~11KB in .rodata.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-03-06 07:50:03 +09:00
parent 50bc8c6136
commit 9ff1aa9d55
5 changed files with 1428 additions and 611 deletions
+1 -2
View File
@@ -41,7 +41,7 @@ module MRuby
allocf.c
readnum.c
readint.c
readfloat.c
fp_uscale.c
state.c
symbol.c
class.c
@@ -66,7 +66,6 @@ module MRuby
cdump.c
codedump.c
print.c
fmt_fp.c
debug.c
etc.c
version.c
-380
View File
@@ -1,380 +0,0 @@
#include <mruby.h>
#include <string.h>
#ifndef MRB_NO_FLOAT
/***********************************************************************
Routine for converting a single-precision
floating-point number into a string.
The code in this function was inspired from Fred Bayer's pdouble.c.
Since pdouble.c was released as Public Domain, I'm releasing this
code as public domain as well.
Dave Hylands
The original code can be found in https://github.com/dhylands/format-float
***********************************************************************/
/***********************************************************************
I modified the routine for mruby:
* support `double`
* support `#` (alt_form) modifier
My modifications in this file are also placed in the public domain.
Matz (Yukihiro Matsumoto)
***********************************************************************/
#include <math.h>
#ifdef MRB_USE_FLOAT32
// 1 sign bit, 8 exponent bits, and 23 mantissa bits.
// exponent values 0 and 255 are reserved, exponent can be 1 to 254.
// exponent is stored with a bias of 127.
// The min and max floats are on the order of 1x10^37 and 1x10^-37
#define FLT_DECEXP 32
#define FLT_ROUND_TO_ONE 0.9999995F
#define FLT_MIN_BUF_SIZE 6 // -9e+99
#else
// 1 sign bit, 11 exponent bits, and 52 mantissa bits.
#define FLT_DECEXP 256
#define FLT_ROUND_TO_ONE 0.999999999995
#define FLT_MIN_BUF_SIZE 7 // -9e+199
#endif /* MRB_USE_FLOAT32 */
static const mrb_float g_pos_pow[] = {
#ifndef MRB_USE_FLOAT32
1e256, 1e128, 1e64,
#endif
1e32, 1e16, 1e8, 1e4, 1e2, 1e1
};
static const mrb_float g_neg_pow[] = {
#ifndef MRB_USE_FLOAT32
1e-256, 1e-128, 1e-64,
#endif
1e-32, 1e-16, 1e-8, 1e-4, 1e-2, 1e-1
};
/*
* mrb_format_float(mrb_float f, char *buf, size_t buf_size, char fmt, int prec, char sign)
*
* fmt: should be one of 'e', 'E', 'f', 'F', 'g', or 'G'. (|0x80 for '#')
* prec: is the precision (as specified in printf)
* sign: should be '\0', '+', or ' ' ('\0' is the normal one - only print
* a sign if ```f``` is negative. Anything else is printed as the
* sign character for positive numbers.
*/
int
mrb_format_float(mrb_float f, char *buf, size_t buf_size, char fmt, int prec, char sign) {
char *s = buf;
int buf_remaining = (int)buf_size - 1;
int alt_form = 0;
if ((uint8_t)fmt & 0x80) {
fmt &= 0x7f; /* turn off alt_form flag */
alt_form = 1;
}
if (buf_size <= FLT_MIN_BUF_SIZE) {
// Smallest exp notion is -9e+99 (-9e+199) which is 6 (7) chars plus terminating
// null.
if (buf_size >= 2) {
*s++ = '?';
}
if (buf_size >= 1) {
*s++ = '\0';
}
return buf_size >= 2;
}
if (signbit(f)) {
*s++ = '-';
f = -f;
}
else if (sign) {
*s++ = sign;
}
buf_remaining -= (int)(s - buf); // Adjust for sign
{
char uc = fmt & 0x20;
if (isinf(f)) {
*s++ = 'I' ^ uc;
*s++ = 'N' ^ uc;
*s++ = 'F' ^ uc;
goto ret;
}
else if (isnan(f)) {
*s++ = 'N' ^ uc;
*s++ = 'A' ^ uc;
*s++ = 'N' ^ uc;
ret:
*s = '\0';
return (int)(s - buf);
}
}
if (prec < 0) {
prec = 6;
}
char e_char = 'E' | (fmt & 0x20); // e_char will match case of fmt
fmt |= 0x20; // Force fmt to be lowercase
char org_fmt = fmt;
if (fmt == 'g' && prec == 0) {
prec = 1;
}
int e, e1;
int dec = 0;
char e_sign = '\0';
int num_digits = 0;
const mrb_float *pos_pow = g_pos_pow;
const mrb_float *neg_pow = g_neg_pow;
if (f == 0.0) {
e = 0;
if (fmt == 'e') {
e_sign = '+';
}
else if (fmt == 'f') {
num_digits = prec + 1;
}
}
else if (f < 1.0) { // f < 1.0
char first_dig = '0';
if (f >= FLT_ROUND_TO_ONE) {
first_dig = '1';
}
// Build negative exponent
for (e = 0, e1 = FLT_DECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) {
if (*neg_pow > f) {
e += e1;
f *= *pos_pow;
}
}
char e_sign_char = '-';
if (f < 1.0) {
if (f >= FLT_ROUND_TO_ONE) {
f = 1.0;
if (e == 0) {
e_sign_char = '+';
}
}
else {
e++;
f *= 10.0;
}
}
// If the user specified 'g' format, and e is <= 4, then we'll switch
// to the fixed format ('f')
if (fmt == 'f' || (fmt == 'g' && e <= 4)) {
fmt = 'f';
dec = -1;
*s++ = first_dig;
if (org_fmt == 'g') {
prec += (e - 1);
}
// truncate precision to prevent buffer overflow
if (prec + 2 > buf_remaining) {
prec = buf_remaining - 2;
}
num_digits = prec;
if (num_digits || alt_form) {
*s++ = '.';
while (--e && num_digits) {
*s++ = '0';
num_digits--;
}
}
}
else {
// For e & g formats, we'll be printing the exponent, so set the
// sign.
e_sign = e_sign_char;
dec = 0;
if (prec > (buf_remaining - FLT_MIN_BUF_SIZE)) {
prec = buf_remaining - FLT_MIN_BUF_SIZE;
if (fmt == 'g') {
prec++;
}
}
}
}
else {
// Build positive exponent
for (e = 0, e1 = FLT_DECEXP; e1; e1 >>= 1, pos_pow++, neg_pow++) {
if (*pos_pow <= f) {
e += e1;
f *= *neg_pow;
}
}
// correct for FP rounding errors in the power-of-10 loop
// (e.g. x87 extended precision can leave f >= 10.0)
if (f >= 10.0) {
f *= 0.1;
e++;
}
// If the user specified fixed format (fmt == 'f') and e makes the
// number too big to fit into the available buffer, then we'll
// switch to the 'e' format.
if (fmt == 'f') {
if (e >= buf_remaining) {
fmt = 'e';
}
else if ((e + prec + 2) > buf_remaining) {
prec = buf_remaining - e - 2;
if (prec < 0) {
// This means no decimal point, so we can add one back
// for the decimal.
prec++;
}
}
}
if (fmt == 'e' && prec > (buf_remaining - 6)) {
prec = buf_remaining - 6;
}
// If the user specified 'g' format, and e is < prec, then we'll switch
// to the fixed format.
if (fmt == 'g' && e < prec) {
fmt = 'f';
prec -= (e + 1);
}
if (fmt == 'f') {
dec = e;
num_digits = prec + e + 1;
}
else {
e_sign = '+';
}
}
if (prec < 0) {
// This can happen when the prec is trimmed to prevent buffer overflow
prec = 0;
}
// We now have f as a floating-point number between >= 1 and < 10
// (or equal to zero), and e contains the absolute value of the power of
// 10 exponent, and (dec + 1) == the number of digits before the decimal.
// For e, prec is # digits after the decimal
// For f, prec is # digits after the decimal
// For g, prec is the max number of significant digits
//
// For e & g there will be a single digit before the decimal
// for f there will be e digits before the decimal
if (fmt == 'e') {
num_digits = prec + 1;
if (prec == 0) prec = 1;
}
else if (fmt == 'g') {
num_digits = prec;
}
// Print the digits of the mantissa
for (int i = 0; i < num_digits; i++,dec--) {
int8_t d = (int8_t)f;
if (d > 9) d = 9;
if (d < 0) d = 0;
*s++ = '0' + d;
if (dec == 0 && (prec > 0 || alt_form)) {
*s++ = '.';
}
f -= (mrb_float)d;
f *= 10.0;
}
// Round
if (f >= 5.0) {
char *rs = s;
rs--;
while (1) {
if (*rs == '.') {
rs--;
continue;
}
if (*rs < '0' || *rs > '9') {
// + or -
rs++; // So we sit on the digit to the right of the sign
break;
}
if (*rs < '9') {
(*rs)++;
break;
}
*rs = '0';
if (rs == buf) {
break;
}
rs--;
}
if (*rs == '0') {
// We need to insert a 1
if (fmt != 'f' && rs[1] == '.') {
// We're going to round 9.99 to 10.00
// Move the decimal point
rs[0] = '.';
rs[1] = '0';
if (e_sign == '-') {
e--;
}
else {
e++;
}
}
s++;
char *ss = s;
while (ss > rs) {
*ss = ss[-1];
ss--;
}
*rs = '1';
if (f < 1.0 && fmt == 'f') {
// We rounded up to 1.0
prec--;
}
}
}
if (org_fmt == 'g' && prec > 0 && !alt_form) {
// Remove trailing zeros and a trailing decimal point
while (s[-1] == '0') {
s--;
}
if (s[-1] == '.') {
s--;
}
}
// Append the exponent
if (e_sign) {
*s++ = e_char;
*s++ = e_sign;
if (e >= 100) {
*s++ = '0' + (e / 100);
e %= 100;
}
*s++ = '0' + (e / 10);
*s++ = '0' + (e % 10);
}
*s = '\0';
return (int)(s - buf);
}
#endif
+1359
View File
File diff suppressed because it is too large Load Diff
-229
View File
@@ -1,229 +0,0 @@
#include <mruby.h>
#ifndef MRB_NO_FLOAT
#include <string.h>
#include <math.h>
#include <stdint.h>
// Powers of 10 lookup table for better performance and accuracy
static const double pow10_positive[] = {
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19,
1e20, 1e21, 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29,
1e30, 1e31, 1e32, 1e33, 1e34, 1e35, 1e36, 1e37, 1e38, 1e39,
1e40, 1e41, 1e42, 1e43, 1e44, 1e45, 1e46, 1e47, 1e48, 1e49,
1e50, 1e51, 1e52, 1e53, 1e54, 1e55, 1e56, 1e57, 1e58, 1e59,
1e60, 1e61, 1e62, 1e63, 1e64, 1e65, 1e66, 1e67, 1e68, 1e69,
1e70, 1e71, 1e72, 1e73, 1e74, 1e75, 1e76, 1e77, 1e78, 1e79,
1e80, 1e81, 1e82, 1e83, 1e84, 1e85, 1e86, 1e87, 1e88, 1e89,
1e90, 1e91, 1e92, 1e93, 1e94, 1e95, 1e96, 1e97, 1e98, 1e99,
1e100, 1e101, 1e102, 1e103, 1e104, 1e105, 1e106, 1e107, 1e108, 1e109,
1e110, 1e111, 1e112, 1e113, 1e114, 1e115, 1e116, 1e117, 1e118, 1e119,
1e120, 1e121, 1e122, 1e123, 1e124, 1e125, 1e126, 1e127, 1e128, 1e129,
1e130, 1e131, 1e132, 1e133, 1e134, 1e135, 1e136, 1e137, 1e138, 1e139,
1e140, 1e141, 1e142, 1e143, 1e144, 1e145, 1e146, 1e147, 1e148, 1e149,
1e150, 1e151, 1e152, 1e153, 1e154, 1e155, 1e156, 1e157, 1e158, 1e159,
1e160, 1e161, 1e162, 1e163, 1e164, 1e165, 1e166, 1e167, 1e168, 1e169,
1e170, 1e171, 1e172, 1e173, 1e174, 1e175, 1e176, 1e177, 1e178, 1e179,
1e180, 1e181, 1e182, 1e183, 1e184, 1e185, 1e186, 1e187, 1e188, 1e189,
1e190, 1e191, 1e192, 1e193, 1e194, 1e195, 1e196, 1e197, 1e198, 1e199,
1e200, 1e201, 1e202, 1e203, 1e204, 1e205, 1e206, 1e207, 1e208, 1e209,
1e210, 1e211, 1e212, 1e213, 1e214, 1e215, 1e216, 1e217, 1e218, 1e219,
1e220, 1e221, 1e222, 1e223, 1e224, 1e225, 1e226, 1e227, 1e228, 1e229,
1e230, 1e231, 1e232, 1e233, 1e234, 1e235, 1e236, 1e237, 1e238, 1e239,
1e240, 1e241, 1e242, 1e243, 1e244, 1e245, 1e246, 1e247, 1e248, 1e249,
1e250, 1e251, 1e252, 1e253, 1e254, 1e255, 1e256, 1e257, 1e258, 1e259,
1e260, 1e261, 1e262, 1e263, 1e264, 1e265, 1e266, 1e267, 1e268, 1e269,
1e270, 1e271, 1e272, 1e273, 1e274, 1e275, 1e276, 1e277, 1e278, 1e279,
1e280, 1e281, 1e282, 1e283, 1e284, 1e285, 1e286, 1e287, 1e288, 1e289,
1e290, 1e291, 1e292, 1e293, 1e294, 1e295, 1e296, 1e297, 1e298, 1e299,
1e300, 1e301, 1e302, 1e303, 1e304, 1e305, 1e306, 1e307, 1e308
};
static const double pow10_negative[] = {
1e0, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-7, 1e-8, 1e-9,
1e-10, 1e-11, 1e-12, 1e-13, 1e-14, 1e-15, 1e-16, 1e-17, 1e-18, 1e-19,
1e-20, 1e-21, 1e-22, 1e-23, 1e-24, 1e-25, 1e-26, 1e-27, 1e-28, 1e-29,
1e-30, 1e-31, 1e-32, 1e-33, 1e-34, 1e-35, 1e-36, 1e-37, 1e-38, 1e-39,
1e-40, 1e-41, 1e-42, 1e-43, 1e-44, 1e-45, 1e-46, 1e-47, 1e-48, 1e-49,
1e-50, 1e-51, 1e-52, 1e-53, 1e-54, 1e-55, 1e-56, 1e-57, 1e-58, 1e-59,
1e-60, 1e-61, 1e-62, 1e-63, 1e-64, 1e-65, 1e-66, 1e-67, 1e-68, 1e-69,
1e-70, 1e-71, 1e-72, 1e-73, 1e-74, 1e-75, 1e-76, 1e-77, 1e-78, 1e-79,
1e-80, 1e-81, 1e-82, 1e-83, 1e-84, 1e-85, 1e-86, 1e-87, 1e-88, 1e-89,
1e-90, 1e-91, 1e-92, 1e-93, 1e-94, 1e-95, 1e-96, 1e-97, 1e-98, 1e-99,
1e-100, 1e-101, 1e-102, 1e-103, 1e-104, 1e-105, 1e-106, 1e-107, 1e-108, 1e-109,
1e-110, 1e-111, 1e-112, 1e-113, 1e-114, 1e-115, 1e-116, 1e-117, 1e-118, 1e-119,
1e-120, 1e-121, 1e-122, 1e-123, 1e-124, 1e-125, 1e-126, 1e-127, 1e-128, 1e-129,
1e-130, 1e-131, 1e-132, 1e-133, 1e-134, 1e-135, 1e-136, 1e-137, 1e-138, 1e-139,
1e-140, 1e-141, 1e-142, 1e-143, 1e-144, 1e-145, 1e-146, 1e-147, 1e-148, 1e-149,
1e-150, 1e-151, 1e-152, 1e-153, 1e-154, 1e-155, 1e-156, 1e-157, 1e-158, 1e-159,
1e-160, 1e-161, 1e-162, 1e-163, 1e-164, 1e-165, 1e-166, 1e-167, 1e-168, 1e-169,
1e-170, 1e-171, 1e-172, 1e-173, 1e-174, 1e-175, 1e-176, 1e-177, 1e-178, 1e-179,
1e-180, 1e-181, 1e-182, 1e-183, 1e-184, 1e-185, 1e-186, 1e-187, 1e-188, 1e-189,
1e-190, 1e-191, 1e-192, 1e-193, 1e-194, 1e-195, 1e-196, 1e-197, 1e-198, 1e-199,
1e-200, 1e-201, 1e-202, 1e-203, 1e-204, 1e-205, 1e-206, 1e-207, 1e-208, 1e-209,
1e-210, 1e-211, 1e-212, 1e-213, 1e-214, 1e-215, 1e-216, 1e-217, 1e-218, 1e-219,
1e-220, 1e-221, 1e-222, 1e-223, 1e-224, 1e-225, 1e-226, 1e-227, 1e-228, 1e-229,
1e-230, 1e-231, 1e-232, 1e-233, 1e-234, 1e-235, 1e-236, 1e-237, 1e-238, 1e-239,
1e-240, 1e-241, 1e-242, 1e-243, 1e-244, 1e-245, 1e-246, 1e-247, 1e-248, 1e-249,
1e-250, 1e-251, 1e-252, 1e-253, 1e-254, 1e-255, 1e-256, 1e-257, 1e-258, 1e-259,
1e-260, 1e-261, 1e-262, 1e-263, 1e-264, 1e-265, 1e-266, 1e-267, 1e-268, 1e-269,
1e-270, 1e-271, 1e-272, 1e-273, 1e-274, 1e-275, 1e-276, 1e-277, 1e-278, 1e-279,
1e-280, 1e-281, 1e-282, 1e-283, 1e-284, 1e-285, 1e-286, 1e-287, 1e-288, 1e-289,
1e-290, 1e-291, 1e-292, 1e-293, 1e-294, 1e-295, 1e-296, 1e-297, 1e-298, 1e-299,
1e-300, 1e-301, 1e-302, 1e-303, 1e-304, 1e-305, 1e-306, 1e-307, 1e-308, 1e-309,
1e-310, 1e-311, 1e-312, 1e-313, 1e-314, 1e-315, 1e-316, 1e-317, 1e-318, 1e-319,
1e-320, 1e-321, 1e-322, 1e-323
};
#define POW10_POSITIVE_SIZE (sizeof(pow10_positive) / sizeof(pow10_positive[0]))
#define POW10_NEGATIVE_SIZE (sizeof(pow10_negative) / sizeof(pow10_negative[0]))
static double
mrb_pow10(int exp)
{
if (exp >= 0) {
if (exp < (int)POW10_POSITIVE_SIZE) {
return pow10_positive[exp];
}
return HUGE_VAL;
}
else {
exp = -exp;
if (exp < (int)POW10_NEGATIVE_SIZE) {
return pow10_negative[exp];
}
return 0.0;
}
}
/*
** Parses a string representation of a floating-point number.
**
** @param str The input string to parse.
** @param endp A pointer to a char* that will be updated to point to the
** character in str after the last character used in the
** conversion. Can be NULL.
** @param fp A pointer to a double that will be set to the parsed
** floating-point number.
** @return TRUE if a float is successfully parsed, FALSE otherwise.
*/
MRB_API mrb_bool
mrb_read_float(const char *str, char **endp, double *fp)
{
const char *p = str;
const char *a = p;
uint64_t int_part = 0;
uint64_t frac_part = 0;
int frac_digits = 0;
int sign = 1;
int digits = 0;
int overflow = 0;
// Skip whitespace
while (ISSPACE((unsigned char)*p)) p++;
// Handle sign
if (*p == '-') { sign = -1; p++; }
else if (*p == '+') p++;
// Parse integer part using integer arithmetic for better accuracy
while (ISDIGIT(*p)) {
if (int_part > (UINT64_MAX - 9) / 10) {
overflow = 1;
// Continue parsing to find the end
while (ISDIGIT(*p)) p++;
break;
}
int_part = int_part * 10 + (*p - '0');
digits++;
a = ++p;
}
// Parse fractional part
if (*p == '.') {
p++;
while (ISDIGIT(*p) && frac_digits < 18) { // Limit precision to avoid overflow
frac_part = frac_part * 10 + (*p - '0');
frac_digits++;
digits++;
p++;
}
// Skip remaining fractional digits if any
while (ISDIGIT(*p)) p++;
a = p;
}
// If no digits were found, return FALSE
if (digits == 0) {
if (endp) *endp = (char*)str;
*fp = 0.0;
return FALSE;
}
double res;
if (overflow) {
// For overflow case, fall back to floating point parsing
res = (double)int_part;
}
else {
// Divide by the exact 10^n (exact for n <= 22) rather than multiplying
// by the inexact 10^-n, so the fraction is correctly rounded.
res = (double)int_part;
if (frac_digits > 0) {
res += (double)frac_part / mrb_pow10(frac_digits);
}
}
// Handle exponent
if ((*p | 32) == 'e') {
int e = 0;
int exp_sign = 1;
p++;
if (*p == '-') { exp_sign = -1; p++; }
else if (*p == '+') p++;
// If no digits follow 'e', ignore the exponent part
if (!ISDIGIT(*p)) goto done;
while (ISDIGIT(*p)) {
if (e < 10000) { // Prevent integer overflow
e = e * 10 + (*p - '0');
}
p++;
}
e *= exp_sign;
// Apply exponent directly - let mrb_pow10 handle overflow
// Large exponents will return HUGE_VAL (infinity) or 0.0 as appropriate
res *= mrb_pow10(e);
a = p;
}
// Apply sign
res *= sign;
// Set endp
done:
if (endp) *endp = (char*)a;
*fp = res;
// strtod(3) stores ERANGE to errno for overflow/underflow
// mruby does not require those checks
#if 0
// Check for underflow after applying the exponent
if (res != 0.0 && fabs(res) < DBL_MIN) {
return FALSE;
}
// Check if the result is infinity or NaN
if (isinf(res) || isnan(res)) {
return FALSE;
}
#endif
return TRUE;
}
#endif
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
# Generate pow10 table for mruby unrounded scaling (fp_uscale.c)
#
# Uses exact integer arithmetic only.
# For each p in [-343, 341], computes (hi, lo) such that:
# 10^p ~= (hi * 2^64 - lo) * 2^pe
# where pe = floor(p * log2(10)) - 127
#
# The 128-bit value pm = hi * 2^64 - lo is in [2^127, 2^128).
POW10_MIN = -343
POW10_MAX = 341
def generate_entry(p)
if p >= 0
val = 10**p
bit_len = val.bit_length
pe = bit_len - 128
if pe >= 0
mask = (1 << pe) - 1
pm = (val >> pe) + ((val & mask) != 0 ? 1 : 0)
else
pm = val << (-pe)
end
else
abs_p = -p
denom = 10**abs_p
# pe = floor(p * log2(10)) - 127
# Ruby's integer division of negative numbers does floor division
pe_est = (p * 108853 >> 15) - 127
numerator = 1 << (-pe_est)
pm = (numerator + denom - 1) / denom
# Adjust pe if pm is out of range [2^127, 2^128)
while pm >= (1 << 128)
pe_est += 1
numerator = 1 << (-pe_est)
pm = (numerator + denom - 1) / denom
end
while pm < (1 << 127)
pe_est -= 1
numerator = 1 << (-pe_est)
pm = (numerator + denom - 1) / denom
end
end
raise "pm out of range for p=#{p}: #{pm.bit_length}" unless pm.bit_length == 128
hi = (pm >> 64) + ((pm & ((1 << 64) - 1)) != 0 ? 1 : 0)
lo = (hi << 64) - pm
raise "hi out of range for p=#{p}" unless hi >= (1 << 63) && hi < (1 << 64)
{ hi: hi, lo: lo }
end
def main
entries = (POW10_MIN..POW10_MAX).map { |p| [p, generate_entry(p)] }
entries.each do |p, e|
printf(" {0x%016xULL, 0x%016xULL},\n", e[:hi], e[:lo])
end
end
main