ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
* [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).