Skip to content

Float#to_s diverges from MRI: missing mantissa fraction, and the wrong fixed/exponential threshold #619

Closed

Description

@tannevaled

Float#to_s/#inspect (internal/object/object.go, func (f Float) ToS) formats with Go's strconv.FormatFloat(v, 'g', -1, 64) and then appends .0 only when the result contains no ., e or E. That diverges from MRI in two independent ways.

1. The mantissa loses its fraction in exponential form

source MRI 4.0.5 rbgo
1e-06 1.0e-06 1e-06
1e+20 1.0e+20 1e+20
-1e-06 -1.0e-06 -1e-06
5e-324 5.0e-324 5e-324

Ruby always shows a fractional digit; the .0 is appended only in fixed form today, so 1e-06 keeps a bare mantissa.

2. The fixed/exponential threshold is Go's, not Ruby's

Go's %g switches to exponential by comparing the exponent against the number of significant digits. MRI's flo_to_s (numeric.c) switches on where the decimal point falls: exponential iff decpt < -3 || decpt > DBL_DIG (15).

Measured against ruby 4.0.5, the boundaries are exactly:

1e14   -> 100000000000000.0      decpt 15, fixed
1e15   -> 1.0e+15                decpt 16, exponential
1e-4   -> 0.0001                 decpt -3, fixed
1e-5   -> 1.0e-05                decpt -4, exponential
123456789012345.0   -> 123456789012345.0       decpt 15, fixed
1234567890123456.0  -> 1.234567890123456e+15   decpt 16, exponential

The visible symptom is 1e6: MRI prints 1000000.0, rbgo prints 1e+06.

Suggested shape

Take the shortest round-tripping digits from strconv.FormatFloat(v, 'e', -1, 64), derive decpt from its exponent, then choose fixed ('f', -1) or exponential by MRI's rule, and in exponential form ensure the mantissa carries at least one fractional digit. Go's exponent padding already matches (e+06, e-05, e+100, e-323).

Why it is worth a dedicated pass

The blast radius is wide — every pinned test and spec that prints a float — so it wants the full go test ./... gate and probably some pinned-value updates in the same change. It was found while landing #617 (core/process) and deliberately left out of that PR, which owns unrelated files.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions