8000 [Bug #20662] Preserve payloads in packed NaN by nobu · Pull Request #11352 · ruby/ruby · GitHub
[go: up one dir, main page]

Skip to content

[Bug #20662] Preserve payloads in packed NaN #11352

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -11505,10 +11505,13 @@ pack.$(OBJEXT): $(CCAN_DIR)/str/str.h
pack.$(OBJEXT): $(hdrdir)/ruby/ruby.h
pack.$(OBJEXT): $(top_srcdir)/internal/array.h
pack.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h
pack.$(OBJEXT): $(top_srcdir)/internal/bignum.h
pack.$(OBJEXT): $(top_srcdir)/internal/bits.h
pack.$(OBJEXT): $(top_srcdir)/internal/compilers.h
pack.$(OBJEXT): $(top_srcdir)/internal/fixnum.h
pack.$(OBJEXT): $(top_srcdir)/internal/gc.h
pack.$(OBJEXT): $(top_srcdir)/internal/imemo.h
pack.$(OBJEXT): $(top_srcdir)/internal/numeric.h
pack.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h
pack.$(OBJEXT): $(top_srcdir)/internal/serial.h
pack.$(OBJEXT): $(top_srcdir)/internal/static_assert.h
Expand Down
1 change: 1 addition & 0 deletions internal/numeric.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ VALUE rb_num_pow(VALUE x, VALUE y);
VALUE rb_float_ceil(VALUE num, int ndigits);
VALUE rb_float_floor(VALUE x, int ndigits);
VALUE rb_float_abs(VALUE flt);
VALUE rb_float_new_from_ptr(const double *d);
static inline VALUE rb_num_compare_with_zero(VALUE num, ID mid);
static inline int rb_num_positive_int_p(VALUE num);
static inline int rb_num_negative_int_p(VALUE num);
Expand Down
16 changes: 7 additions & 9 deletions numeric.c
Original file line number Diff line number Diff line change
Expand Up @@ -1022,18 +1022,16 @@ num_negative_p(VALUE num)

VALUE
rb_float_new_in_heap(double d)
{
return rb_float_new_from_ptr(&d);
}

VALUE
rb_float_new_from_ptr(const double *d)
{
NEWOBJ_OF(flt, struct RFloat, rb_cFloat, T_FLOAT | (RGENGC_WB_PROTECTED_FLOAT ? FL_WB_PROTECTED : 0), sizeof(struct RFloat), 0);

#if SIZEOF_DOUBLE <= SIZEOF_VALUE
flt->float_value = d;
#else
union {
double d;
rb_float_value_type v;
} u = {d};
flt->float_value = u.v;
#endif
memcpy(&flt->float_value, d, sizeof(*d));
OBJ_FREEZE((VALUE)flt);
return (VALUE)flt;
}
Expand Down
107 changes: 77 additions & 30 deletions pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "internal.h"
#include "internal/array.h"
#include "internal/bits.h"
#include "internal/numeric.h"
#include "internal/string.h"
#include "internal/symbol.h"
#include "internal/variable.h"
Expand Down Expand Up @@ -173,23 +174,73 @@ unknown_directive(const char *mode, char type, VALUE fmt)
mode, unknown, fmt);
}

static float
VALUE_to_float(VALUE obj)
/* convert float <=> double as IEEE 754-2008, with keeping quiet bit */
static const uint32_t F32_QBIT = (uint32_t)1U << (FLT_MANT_DIG-2);
static const uint64_t F64_QBIT = (uint64_t)1U << (DBL_MANT_DIG-2);
static const uint32_t F32_MANT = ~((uint32_t)~0U << (FLT_MANT_DIG-1));
static const int F64_QBIT_OFFSET = DBL_MANT_DIG - FLT_MANT_DIG;

typedef union {
float f;
double d;
uint32_t u32;
uint64_t u64;
} float_double_conv;

static inline VALUE
unpack_flt2num(uint32_t u)
{
float_double_conv tmp = {.u32 = u};
tmp.d = tmp.f;
if (isnan(tmp.d)) {
uint64_t qbit = ((uint64_t)(u & F32_QBIT) << F64_QBIT_OFFSET);
tmp.u64 = (tmp.u64 & ~F64_QBIT) | qbit;
}
return rb_float_new_from_ptr(&tmp.d);
}

static inline VALUE
unpack_dbl2num(uint64_t u)
{
float_double_conv tmp = {.u64 = u};
return rb_float_new_from_ptr(&tmp.d);
}

static void
VALUE_to_float(VALUE obj, FLOAT_SWAPPER *result)
{
VALUE v = rb_to_float(obj);
double d = RFLOAT_VALUE(v);

if (isnan(d)) {
return NAN;
/* Assume endians of double and uint64_t are same */
volatile uint64_t *u = (void *)&RFLOAT(v)->float_value;
uint32_t qbit = (uint32_t)((*u & F64_QBIT) >> F64_QBIT_OFFSET);
FLOAT_SWAPPER f = {.f = (float)d};
f.u = (f.u & ~F32_QBIT) | qbit;
if (!(f.u & F32_MANT)) f.u |= 1;
*result = f;
}
else if (d < -FLT_MAX) {
return -INFINITY;
result->f = -INFINITY;
}
else if (d <= FLT_MAX) {
return d;
result->f = d;
}
else {
return INFINITY;
result->f = INFINITY;
}
}

static void
VALUE_to_double(VALUE obj, DOUBLE_SWAPPER *result)
{
VALUE v = rb_to_float(obj);
if (FLONUM_P(v)) {
result->d = rb_float_value(v);
}
else {
memcpy(result->buf, &RFLOAT(v)->float_value, sizeof(double));
}
}

Expand Down Expand Up @@ -553,20 +604,18 @@ pack_pack(rb_e E864 xecution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer)
case 'f': /* single precision float in native format */
case 'F': /* ditto */
while (len-- > 0) {
float f;

FLOAT_CONVWITH(tmp);
from = NEXTFROM;
f = VALUE_to_float(from);
rb_str_buf_cat(res, (char*)&f, sizeof(float));
VALUE_to_float(from, &tmp);
rb_str_buf_cat(res, tmp.buf, sizeof(float));
}
break;

case 'e': /* single precision float in VAX byte-order */
while (len-- > 0) {
FLOAT_CONVWITH(tmp);

from = NEXTFROM;
tmp.f = VALUE_to_float(from);
VALUE_to_float(from, &tmp);
HTOVF(tmp);
rb_str_buf_cat(res, tmp.buf, sizeof(float));
}
Expand All @@ -576,7 +625,7 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer)
while (len-- > 0) {
DOUBLE_CONVWITH(tmp);
from = NEXTFROM;
tmp.d = RFLOAT_VALUE(rb_to_float(from));
VALUE_to_double(from, &tmp);
HTOVD(tmp);
rb_str_buf_cat(res, tmp.buf, sizeof(double));
}
Expand All @@ -585,19 +634,18 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer)
case 'd': /* double precision float in native format */
case 'D': /* ditto */
while (len-- > 0) {
double d;

DOUBLE_CONVWITH(tmp);
from = NEXTFROM;
d = RFLOAT_VALUE(rb_to_float(from));
rb_str_buf_cat(res, (char*)&d, sizeof(double));
VALUE_to_double(from, &tmp);
rb_str_buf_cat(res, tmp.buf, sizeof(double));
}
break;

case 'g': /* single precision float in network byte-order */
while (len-- > 0) {
FLOAT_CONVWITH(tmp);
from = NEXTFROM;
tmp.f = VALUE_to_float(from);
VALUE_to_float(from, &tmp);
HTONF(tmp);
rb_str_buf_cat(res, tmp.buf, sizeof(float));
}
Expand All @@ -606,9 +654,8 @@ pack_pack(rb_execution_context_t *ec, VALUE ary, VALUE fmt, VALUE buffer)
case 'G': /* double precision float in network byte-order */
while (len-- > 0) {
DOUBLE_CONVWITH(tmp);

from = NEXTFROM;
tmp.d = RFLOAT_VALUE(rb_to_float(from));
VALUE_to_double(from, &tmp);
HTOND(tmp);
rb_str_buf_cat(res, tmp.buf, sizeof(double));
}
Expand Down Expand Up @@ -1291,9 +1338,9 @@ pack_unpack_internal(VALUE str, VALUE fmt, enum unpack_mode mode, long offset)
case 'F':
PACK_LENGTH_ADJUST_SIZE(sizeof(float));
while (len-- > 0) {
float tmp;
UNPACK_FETCH(&tmp, float);
UNPACK_PUSH(DBL2NUM((double)tmp));
FLOAT_CONVWITH(tmp);
UNPACK_FETCH(&tmp.buf, float);
UNPACK_PUSH(unpack_flt2num(tmp.u));
}
PACK_ITEM_ADJUST();
break;
Expand All @@ -1304,7 +1351,7 @@ pack_unpack_internal(VALUE str, VALUE fmt, enum unpack_mode mode, long offset)
FLOAT_CONVWITH(tmp);
UNPACK_FETCH(tmp.buf, float);
VTOHF(tmp);
UNPACK_PUSH(DBL2NUM(tmp.f));
UNPACK_PUSH(unpack_flt2num(tmp.u));
}
PACK_ITEM_ADJUST();
break;
Expand All @@ -1315,7 +1362,7 @@ pack_unpack_internal(VALUE str, VALUE fmt, enum unpack_mode mode, long offset)
DOUBLE_CONVWITH(tmp);
UNPACK_FETCH(tmp.buf, double);
VTOHD(tmp);
UNPACK_PUSH(DBL2NUM(tmp.d));
UNPACK_PUSH(unpack_dbl2num(tmp.u));
}
PACK_ITEM_ADJUST();
break;
Expand All @@ -1324,9 +1371,9 @@ pack_unpack_internal(VALUE str, VALUE fmt, enum unpack_mode mode, long offset)
case 'd':
PACK_LENGTH_ADJUST_SIZE(sizeof(double));
while (len-- > 0) {
double tmp;
UNPACK_FETCH(&tmp, double);
UNPACK_PUSH(DBL2NUM(tmp));
DOUBLE_CONVWITH(tmp);
UNPACK_FETCH(&tmp.buf, double);
UNPACK_PUSH(unpack_dbl2num(tmp.u));
}
PACK_ITEM_ADJUST();
break;
Expand All @@ -1337,7 +1384,7 @@ pack_unpack_internal(VALUE str, VALUE fmt, enum unpack_mode mode, long offset)
FLOAT_CONVWITH(tmp);
UNPACK_FETCH(tmp.buf, float);
NTOHF(tmp);
UNPACK_PUSH(DBL2NUM(tmp.f));
UNPACK_PUSH(unpack_flt2num(tmp.u));
}
PACK_ITEM_ADJUST();
break;
Expand All @@ -1348,7 +1395,7 @@ pack_unpack_internal(VALUE str, VALUE fmt, enum unpack_mode mode, long offset)
DOUBLE_CONVWITH(tmp);
UNPACK_FETCH(tmp.buf, double);
NTOHD(tmp);
UNPACK_PUSH(DBL2NUM(tmp.d));
UNPACK_PUSH(unpack_dbl2num(tmp.u));
}
PACK_ITEM_ADJUST();
break;
Expand Down
19 changes: 18 additions & 1 deletion test/ruby/test_pack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -532,16 +532,33 @@ def test_pack_unpack_fdeEgG
nan = inf/inf
[0.0, 1.0, 3.0, inf, -inf, nan].each do |x|
%w(f d e E g G).each do |f|
v = [x].pack(f).unpack(f)
packed = [x].pack(f)
v = packed.unpack(f)
if x.nan?
assert_predicate(v.first, :nan?)
assert_equal(packed, v.pack(f))
else
assert_equal([x], v)
end
end
end
end

def test_pack_unpack_float_nan
snan = [0x7fc00000, 0x7fffe000, 0x7fbfe000, 0x7ffff000, 0x7fbff000]
snan.concat(snan.map {|x| 0x80000000 | x}) # negative
snan.each do |x|
msg = "nan(%x)" % x
packed = [x].pack("L")
f = packed.unpack1("f")

assert_predicate(f, :nan?, msg)
assert_equal packed, [f].pack("f"), msg
b64 = [((x & 0xff80_0000) << 32) | 0x70_0000_0000_0000 | ((x & 0x007f_ffff) << 29)].pack("Q")
assert_equal b64, [f].pack("d"), msg
end
end

def test_pack_unpack_x
assert_equal("", [].pack("x0"))
assert_equal("\0", [].pack("x"))
Expand Down
Loading
0