4

In a JSON object, given the string "0.0000086900" as the value of a key-value pair, if I do |tonumber on this, 8.69e-06 gets returned.

How can I ensure that only decimals are ever returned? In the case above, this would be 0.0000086900

SOLUTION (based on Peak's code snippet below)

def to_decimal:
  def rpad(n): if (n > length) then . + ((n - length) * "0") else . end;
  def lpad(n): if (n > length) then ((n - length) * "0") + . else . end;

tostring
  | . as $s
  | [match( "(?<sgn>[+-]?)(?<left>[0-9]*)(?<p>\\.?)(?<right>[0-9]*)(?<e>[Ee]?)(?<exp>[+-]?[0-9]+)" )
      .captures[].string] as [$sgn, $left, $p, $right, $e, $exp]
  | if $e == "" then .
    else ($exp|tonumber) as $exp
    | ($left|length) as $len
    | if $exp < 0 then "0." + ($left | lpad(1 - $exp - $len)) + $right
      else ($left | rpad($exp - $len)) + "." + $right
      end
      | $sgn + .
    end;
John S.
  • 501
  • 2
  • 6
  • 17
  • Possible duplicate of [jq reformatting decimals in scientific notation -- can this be avoided?](https://stackoverflow.com/questions/42956806/jq-reformatting-decimals-in-scientific-notation-can-this-be-avoided) – Jeff Mercado Mar 26 '18 at 23:33

1 Answers1

4

Unfortunately there is currently no way in jq to modify the representation of JSON numbers as such; the best one can do is modify their representation as strings. Here is a simple illustration of what can be done.

to_decimal takes as input a JSON number (or a valid JSON string representation of a number, possibly with a leading "+") as input, and converts it into a suitable string, e.g.:

["0","1","-1","123","-123",".00000123","1230000","-.00000123","-0.123","0.123","0.001"]

produces:

[0,"0"]
["+1","1"]
[-1,"-1"]
[123,"123"]
[-123,"-123"]
[1.23e-06,".00000123"]
[1230000,"1230000"]
[-1.23e-06,"-.00000123"]
[-0.123,"-0.123"]
[0.123,"0.123"]
[0.001,"0.001"]

Notice that any leading "+" is dropped.

to_decimal

def to_decimal:
  def rpad(n): if (n > length) then . + ((n - length) * "0") else . end;
  def lpad(n): if (n > length) then ((n - length) * "0") + . else . end;

  tostring
  | . as $s
  | capture( "(?<sgn>[+-]?)(?<left>[0-9]*)(?<p>\\.?)(?<right>[0-9]*)(?<e>[Ee]?)(?<exp>[+-]?[0-9]+)" )
  | if .e == "" then (if .sgn == "+" then $s[1:] else $s end)
    else (.left|length) as $len
    | (.exp|tonumber) as $exp
    | (if .sgn == "-" then "-" else "" end ) as $sgn
    | if $exp < 0 then "." + (.left | lpad(1 - $exp - $len)) + .right
      else (.left | rpad($exp - $len)) + "." + .right
      end
      | $sgn + .
    end ;
peak
  • 105,803
  • 17
  • 152
  • 177
  • There were a number of cases where your function didn't work properly, e.g. -1.23e-6. I have fixed this, as well as the typo [+=] -> [+-], and normalisation of decimals with a fractional amount only (we had .45 -> 0.45 but 1.23e-6 -> .00000123. Now all purely fractional numbers start with '0.', including negative ones). – John S. Mar 31 '18 at 22:35
  • @John S. -- Thanks for pointing out the typo, which has been fixed. I believe all the other issues have also been addressed. – peak Apr 12 '21 at 19:34