Module: Ruby::Rego::GoNumberFormat

Defined in:
lib/ruby/rego/go_number_format.rb

Overview

Renders a number’s shortest significant digits the way Go’s strconv / math/big ‘g’ verb does (the format OPA’s FloatToNumber applies to a non-integer big.Float result): shortest digits, with scientific notation when the decimal exponent is < -4 or >= 6, and an exponent that is always signed and zero-padded to at least two digits (1e-05, 1.2345675e+06). Integer-valued results are formatted by the caller as a full decimal (Go’s ‘f’ verb), so this module only renders the fractional (‘g’) case.

Both the arbitrary-precision Number (flt-sourced digits) and the YAML emitter (Ruby Float#to_s digits) share this single Go-faithful renderer.

Class Method Summary collapse

Class Method Details

.fixed(digits, point) ⇒ String

Returns:

  • (String)


58
59
60
61
62
63
64
# File 'lib/ruby/rego/go_number_format.rb', line 58

def self.fixed(digits, point)
  length = digits.length
  return "0.#{"0" * -point}#{digits}" if point <= 0
  return digits + ("0" * (point - length)) if point >= length

  "#{digits[0...point]}.#{digits[point..]}"
end

.render(digits, point, negative) ⇒ String

Render shortest digits + decimal-point position as Go’s ‘g’ verb would.

Parameters:

  • digits (String)

    shortest significant digits, no leading/trailing zeros

  • point (Integer)

    decimal-point position such that value == 0. * 10**point

  • negative (Boolean)

    whether the value is negative

Returns:

  • (String)


21
22
23
24
25
# File 'lib/ruby/rego/go_number_format.rb', line 21

def self.render(digits, point, negative)
  exponent = point - 1
  body = exponent < -4 || exponent >= 6 ? scientific(digits, exponent) : fixed(digits, point)
  negative ? "-#{body}" : body
end

.scientific(digits, exponent) ⇒ String

Returns:

  • (String)


67
68
69
70
# File 'lib/ruby/rego/go_number_format.rb', line 67

def self.scientific(digits, exponent)
  mantissa = digits.length == 1 ? digits : "#{digits[0]}.#{digits[1..]}"
  "#{mantissa}e#{exponent.negative? ? "-" : "+"}#{format("%02d", exponent.abs)}"
end

.shortest_digits(string) ⇒ Array(String, Integer)

Parse a shortest-form numeric string (Ruby Float#to_s or flt’s free format, decimal or scientific, case-insensitive e) into [digits, point] such that the magnitude equals 0. * 10**point. A sign, if present, is ignored — callers pass it to #render separately.

rubocop:disable Metrics/AbcSize

Parameters:

  • string (String)

Returns:

  • (Array(String, Integer))


34
35
36
37
38
39
40
41
42
43
# File 'lib/ruby/rego/go_number_format.rb', line 34

def self.shortest_digits(string)
  mantissa, exponent = string.sub(/\A-/, "").split(/e/i)
  integer_part, fraction = mantissa.to_s.split(".")
  integer_part = integer_part.to_s
  combined = integer_part + fraction.to_s
  without_leading = combined.sub(/\A0+/, "")
  point = integer_part.length + (exponent || "0").to_i - (combined.length - without_leading.length)
  digits = strip_trailing_zeros(without_leading)
  digits.empty? ? ["0", 1] : [digits, point]
end

.strip_trailing_zeros(string) ⇒ String

Drop trailing ‘0’ digits via a single reverse scan to the last significant digit. Avoids an anchored /0+\z/ (which a regex engine can match in polynomial time on adversarial all-zero runs); the negated single-character class has no quantifier to backtrack on.

Parameters:

  • string (String)

Returns:

  • (String)


52
53
54
55
# File 'lib/ruby/rego/go_number_format.rb', line 52

def self.strip_trailing_zeros(string)
  last_significant = string.rindex(/[^0]/)
  last_significant ? string[0..last_significant].to_s : ""
end