Category:Jq/rational.jq
Appearance
module { "name": "rational", "description": "rational numbers", "version": "2024.07.14", "homepage": "https://rosettacode.org/w/index.php?title=Category:Jq/rational.jq", "license": "MIT", "author": "pkoppstein at gmail dot com", }; # In this documentation, "Rational" refers to a reduced-form rational # represented by a JSON object of the form {n:$n d:$d} where # n signifies the numerator and d the denominator, and $d > 0. # The notation $p // $q is also used, and this is the form used for pretty-printing. # r/2 can be thought of as a constructor but can also be used for normalization # and to divide one rational by another. # All the "r"-prefixed functions defined here are polymorphic in the # sense that an integer can be used in place of the corresponding Rational. # NEWS: 2024.07.14: r/2 uses idivide # gcd(a; b): # idivide($q): # power($b): # r($p;$q): # rabs: # radd($a; $b): # radd: # rdiv($a; $b): # requal($a; $b): # rgreaterthan($q): # rgreaterthanOrEqual($q): # rinv: # rlessthan($q): # rlessthanOrEqual($q): # rminus($a; $b): # rminus: # rmult($a; $b): # rmult: # rpp: # rround(precision): # rsqrt(precision): # r_to_decimal(precision): # a and b are assumed to be non-zero integers def gcd(a; b): # subfunction expects [a,b] as input # i.e. a ~ .[0] and b ~ .[1] def rgcd: if .[1] == 0 then .[0] else [.[1], .[0] % .[1]] | rgcd end; [a,b] | rgcd; # To take advantage of gojq's support for accurate integer division: def idivide($j): (. % $j) as $mod | (. - $mod) / $j ; def power($b): . as $in | reduce range(0;$b) as $i (1; . * $in); # $p should be an integer or a rational # $q should be a non-zero integer or a rational # Output: a Rational: $p // $q def r($p;$q): def r: if type == "number" then {n: ., d: 1} else . end; # The remaining subfunctions assume all args are Rational def n: if .d < 0 then {n: -.n, d: -.d} else . end; def rdiv($a;$b): ($a.d * $b.n) as $denom | if $denom==0 then "r: division by 0" | error else r($a.n * $b.d; $denom) end; if $q == 1 and ($p|type) == "number" then {n: $p, d: 1} elif $q == 0 then "r: denominator cannot be 0" | error else if ($p|type == "number") and ($q|type == "number") then gcd($p;$q) as $g | {n: ($p|idivide($g)), d: ($q|idivide($g))} | n else rdiv($p|r; $q|r) end end; # Polymorphic (integers and rationals in general) def requal($a; $b): if $a | type == "number" and $b | type == "number" then $a == $b else r($a;1) == r($b;1) end; # Input: a Rational # Output: a Rational with a denominator that has no more than $digits digits # and such that |rBefore - rAfter| < 1/(10|power($digits) # where $digits should be a positive integer. def rround($digits): if .d | length > $digits then (10|power($digits)) as $p | .d as $d | r($p * .n | idivide($d); $p) else . end; # Polymorphic; see also radd/0 def radd($a; $b): def r: if type == "number" then {n: ., d: 1} else . end; ($a|r) as {n: $na, d: $da} | ($b|r) as {n: $nb, d: $db} | r( ($na * $db) + ($nb * $da); $da * $db ); # Polymorphic; see also rmult/0 def rmult($a; $b): def r: if type == "number" then {n: ., d: 1} else . end; ($a|r) as {n: $na, d: $da} | ($b|r) as {n: $nb, d: $db} | r( $na * $nb; $da * $db ) ; # Input: an array of rationals (integers and/or Rationals) # Output: a Rational computed using left-associativity def rmult: if length == 0 then r(1;1) elif length == 1 then r(.[0]; 1) # ensure the result is Rational else .[0] as $first | reduce .[1:][] as $x ($first; rmult(.; $x)) end; # Input: an array of rationals (integers and/or Rationals) # Output: a Rational computed using left-associativity def radd: if length == 0 then r(0;1) elif length == 1 then r(.[0]; 1) # ensure the result is Rational else .[0] as $first | reduce .[1:][] as $x ($first; radd(. ; $x)) end; def rabs: r(.;1) | r(.n|length; .d|length); def rminus: r(-1 * .n; .d); def rminus($a; $b): radd($a; rmult(-1; $b)); # Note that rinv does not check for division by 0 def rinv: r(1; .); def rdiv($a; $b): r($a; $b); # Input: an integer or a Rational, $p # Output: $p < $q def rlessthan($q): # lt($b) assumes . and $b have the same sign def lt($b): . as $a | ($a.n * $b.d) < ($b.n * $a.d); if $q|type == "number" then rlessthan(r($q;1)) else if type == "number" then r(.;1) else . end | if .n < 0 then if ($q.n >= 0) then true else . as $p | ($q|rminus | rlessthan($p|rminus)) end else lt($q) end end; def rgreaterthan($q): . as $p | $q | rlessthan($p); def rlessthanOrEqual($q): requal(.;$q) or rlessthan($q); def rgreaterthanOrEqual($q): requal(.;$q) or rgreaterthan($q); # Input: non-negative integer or Rational # This version uses rround to speed things up def rsqrt(precision): r(.;1) as $n | (precision + 1) as $digits | def update: rmult( r(1;2); radd(.x; rdiv($n; .x))) | rround($digits); r(1; 10|power(precision)) as $p | { x: .} | .root = update | until( rminus(.root; .x) | rabs | rlessthan($p); .x = .root | .root = update ) | .root ; # Use native floats # q.v. r_to_decimal(precision) def r_to_decimal: .n / .d; # Input: a Rational, or {n, d} in general, or an integer. # Output: a string representation of the input as a decimal number. # If the input is a number, it is simply converted to a string. # Otherwise, $precision determines the number of digits after the decimal point, # obtained by truncating, but trailing 0s are omitted. # Examples assuming $digits is 5: # -0//1 => "0" # 2//1 => "2" # 1//2 => "0.5" # 1//3 => "0.33333" # 7//9 => "0.77777" # 1//100 => "0.01" # -1//10 => "-0.1" # 1//1000000 => "0." def r_to_decimal($digits): if .n == 0 # captures the annoying case of -0 then "0" elif type == "number" then tostring elif .d < 0 then {n: -.n, d: -.d}|r_to_decimal($digits) elif .n < 0 then "-" + ((.n = -.n) | r_to_decimal($digits)) else (10|power($digits)) as $p | .d as $d | if $d == 1 then .n|tostring else ($p * .n | idivide($d) | tostring) as $n | ($n|length) as $nlength | (if $nlength > $digits then $n[0:$nlength-$digits] + "." + $n[$nlength-$digits:] else "0." + ("0"*($digits - $nlength) + $n) end) | sub("0+$";"") end end; # pretty print ala Julia def rpp: "\(.n) // \(.d)";
This category currently contains no pages or media.