* [ruby-core:122515] [Ruby Bug#21436] Date#ajd returns incorrect positive values due to integer overflow for large negative years
@ 2025-06-11 12:27 Stranger6667 (Dmitry Dygalo) via ruby-core
0 siblings, 0 replies; only message in thread
From: Stranger6667 (Dmitry Dygalo) via ruby-core @ 2025-06-11 12:27 UTC (permalink / raw)
To: ruby-core; +Cc: Stranger6667 (Dmitry Dygalo)
Issue #21436 has been reported by Stranger6667 (Dmitry Dygalo).
----------------------------------------
Bug #21436: Date#ajd returns incorrect positive values due to integer overflow for large negative years
https://bugs.ruby-lang.org/issues/21436
* Author: Stranger6667 (Dmitry Dygalo)
* Status: Open
* ruby -v: 3.2.8 - 3.4.4 (and older versions too)
* Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN
----------------------------------------
`Date#ajd` (astronomical Julian Day number) returns incorrect positive values instead of negative values for certain large negative years due to signed integer overflow in the `RB_INT2FIX` function.
```ruby
require 'date'
# Works correctly
Date.civil(-110823815392979, 2, 19).ajd
#=> (-80956797141128945/2)
# BUG - returns positive instead of negative
Date.civil(-11082381539297990, 2, 19).ajd
#=> (1127692322401036327/2) # Should be negative!
# Works correctly (takes a different code path)
Date.civil(-111111111082381539297990, 2, 19).ajd
#=> (-81166666645679714453739481/2)
```
In [ext/date/date_core.c](https://github.com/ruby/ruby/blob/v3_4_4/ext/date/date_core.c#L1605), the `ir` value can overflow
```c
if (FIXNUM_P(r) && FIX2LONG(r) <= (FIXNUM_MAX / 2)) {
long ir = FIX2LONG(r);
ir = ir * 2 - 1;
// On Date.civil(-11082381539297990, 2, 19).ajd
//
// ir = -8095679714453739481
return rb_rational_new2(LONG2FIX(ir), INT2FIX(2)); // Overflow in LONG2FIX
}
```
Then in [include/ruby/internal/arithmetic/long.h](https://github.com/ruby/ruby/blob/v3_4_4/include/ruby/internal/arithmetic/long.h#L110) (`LONG2FIX` is an alias for `RB_INT2FIX`)
```c
static inline VALUE
RB_INT2FIX(long i)
{
RBIMPL_ASSERT_OR_ASSUME(RB_FIXABLE(i));
/* :NOTE: VALUE can be wider than long. As j being unsigned, 2j+1 is fully
* defined. Also it can be compiled into a single LEA instruction. */
const unsigned long j = RBIMPL_CAST((unsigned long)i);
const unsigned long k = (j << 1) + RUBY_FIXNUM_FLAG;
const long l = RBIMPL_CAST((long)k);
const SIGNED_VALUE m = l; /* Sign extend */
const VALUE n = RBIMPL_CAST((VALUE)m);
RBIMPL_ASSERT_OR_ASSUME(RB_FIXNUM_P(n));
return n;
}
```
It effectively goes:
```
j = (unsigned long)-8095679714453739481 // 10351064359255812135
k = (10351064359255812135 << 1) + RUBY_FIXNUM_FLAG // 2255384644802072655
...
```
So, `2255384644802072655` encodes `1127692322401036327`, which is the numerator for the observed `ajd` return value in the reproducer above.
It also affects `Date` comparisons (since `ajd` is used in `cmp_gen`)
--
https://bugs.ruby-lang.org/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2025-06-11 12:28 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-06-11 12:27 [ruby-core:122515] [Ruby Bug#21436] Date#ajd returns incorrect positive values due to integer overflow for large negative years Stranger6667 (Dmitry Dygalo) via ruby-core
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).