1#!/usr/bin/env julia 2# -*- julia -*- 3 4# remez.jl - implementation of the Remez algorithm for polynomial approximation 5# 6# Copyright (c) 2015-2019, Arm Limited. 7# SPDX-License-Identifier: MIT 8 9import Base.\ 10 11# ---------------------------------------------------------------------- 12# Helper functions to cope with different Julia versions. 13if VERSION >= v"0.7.0" 14 array1d(T, d) = Array{T, 1}(undef, d) 15 array2d(T, d1, d2) = Array{T, 2}(undef, d1, d2) 16else 17 array1d(T, d) = Array(T, d) 18 array2d(T, d1, d2) = Array(T, d1, d2) 19end 20if VERSION < v"0.5.0" 21 String = ASCIIString 22end 23if VERSION >= v"0.6.0" 24 # Use Base.invokelatest to run functions made using eval(), to 25 # avoid "world age" error 26 run(f, x...) = Base.invokelatest(f, x...) 27else 28 # Prior to 0.6.0, invokelatest doesn't exist (but fortunately the 29 # world age problem also doesn't seem to exist) 30 run(f, x...) = f(x...) 31end 32 33# ---------------------------------------------------------------------- 34# Global variables configured by command-line options. 35floatsuffix = "" # adjusted by --floatsuffix 36xvarname = "x" # adjusted by --variable 37epsbits = 256 # adjusted by --bits 38debug_facilities = Set() # adjusted by --debug 39full_output = false # adjusted by --full 40array_format = false # adjusted by --array 41preliminary_commands = array1d(String, 0) # adjusted by --pre 42 43# ---------------------------------------------------------------------- 44# Diagnostic and utility functions. 45 46# Enable debugging printouts from a particular subpart of this 47# program. 48# 49# Arguments: 50# facility Name of the facility to debug. For a list of facility names, 51# look through the code for calls to debug(). 52# 53# Return value is a BigFloat. 54function enable_debug(facility) 55 push!(debug_facilities, facility) 56end 57 58# Print a diagnostic. 59# 60# Arguments: 61# facility Name of the facility for which this is a debug message. 62# printargs Arguments to println() if debugging of that facility is 63# enabled. 64macro debug(facility, printargs...) 65 printit = quote 66 print("[", $facility, "] ") 67 end 68 for arg in printargs 69 printit = quote 70 $printit 71 print($(esc(arg))) 72 end 73 end 74 return quote 75 if $facility in debug_facilities 76 $printit 77 println() 78 end 79 end 80end 81 82# Evaluate a polynomial. 83 84# Arguments: 85# coeffs Array of BigFloats giving the coefficients of the polynomial. 86# Starts with the constant term, i.e. coeffs[i] is the 87# coefficient of x^(i-1) (because Julia arrays are 1-based). 88# x Point at which to evaluate the polynomial. 89# 90# Return value is a BigFloat. 91function poly_eval(coeffs::Array{BigFloat}, x::BigFloat) 92 n = length(coeffs) 93 if n == 0 94 return BigFloat(0) 95 elseif n == 1 96 return coeffs[1] 97 else 98 return coeffs[1] + x * poly_eval(coeffs[2:n], x) 99 end 100end 101 102# Evaluate a rational function. 103 104# Arguments: 105# ncoeffs Array of BigFloats giving the coefficients of the numerator. 106# Starts with the constant term, and 1-based, as above. 107# dcoeffs Array of BigFloats giving the coefficients of the denominator. 108# Starts with the constant term, and 1-based, as above. 109# x Point at which to evaluate the function. 110# 111# Return value is a BigFloat. 112function ratfn_eval(ncoeffs::Array{BigFloat}, dcoeffs::Array{BigFloat}, 113 x::BigFloat) 114 return poly_eval(ncoeffs, x) / poly_eval(dcoeffs, x) 115end 116 117# Format a BigFloat into an appropriate output format. 118# Arguments: 119# x BigFloat to format. 120# 121# Return value is a string. 122function float_to_str(x) 123 return string(x) * floatsuffix 124end 125 126# Format a polynomial into an arithmetic expression, for pasting into 127# other tools such as gnuplot. 128 129# Arguments: 130# coeffs Array of BigFloats giving the coefficients of the polynomial. 131# Starts with the constant term, and 1-based, as above. 132# 133# Return value is a string. 134function poly_to_string(coeffs::Array{BigFloat}) 135 n = length(coeffs) 136 if n == 0 137 return "0" 138 elseif n == 1 139 return float_to_str(coeffs[1]) 140 else 141 return string(float_to_str(coeffs[1]), "+", xvarname, "*(", 142 poly_to_string(coeffs[2:n]), ")") 143 end 144end 145 146# Format a rational function into a string. 147 148# Arguments: 149# ncoeffs Array of BigFloats giving the coefficients of the numerator. 150# Starts with the constant term, and 1-based, as above. 151# dcoeffs Array of BigFloats giving the coefficients of the denominator. 152# Starts with the constant term, and 1-based, as above. 153# 154# Return value is a string. 155function ratfn_to_string(ncoeffs::Array{BigFloat}, dcoeffs::Array{BigFloat}) 156 if length(dcoeffs) == 1 && dcoeffs[1] == 1 157 # Special case: if the denominator is just 1, leave it out. 158 return poly_to_string(ncoeffs) 159 else 160 return string("(", poly_to_string(ncoeffs), ")/(", 161 poly_to_string(dcoeffs), ")") 162 end 163end 164 165# Format a list of x,y pairs into a string. 166 167# Arguments: 168# xys Array of (x,y) pairs of BigFloats. 169# 170# Return value is a string. 171function format_xylist(xys::Array{Tuple{BigFloat,BigFloat}}) 172 return ("[\n" * 173 join([" "*string(x)*" -> "*string(y) for (x,y) in xys], "\n") * 174 "\n]") 175end 176 177# ---------------------------------------------------------------------- 178# Matrix-equation solver for matrices of BigFloat. 179# 180# I had hoped that Julia's type-genericity would allow me to solve the 181# matrix equation Mx=V by just writing 'M \ V'. Unfortunately, that 182# works by translating the inputs into double precision and handing 183# off to an optimised library, which misses the point when I have a 184# matrix and vector of BigFloat and want my result in _better_ than 185# double precision. So I have to implement my own specialisation of 186# the \ operator for that case. 187# 188# Fortunately, the point of using BigFloats is that we have precision 189# to burn, so I can do completely naïve Gaussian elimination without 190# worrying about instability. 191 192# Arguments: 193# matrix_in 2-dimensional array of BigFloats, representing a matrix M 194# in row-first order, i.e. matrix_in[r,c] represents the 195# entry in row r col c. 196# vector_in 1-dimensional array of BigFloats, representing a vector V. 197# 198# Return value: a 1-dimensional array X of BigFloats, satisfying M X = V. 199# 200# Expects the input to be an invertible square matrix and a vector of 201# the corresponding size, on pain of failing an assertion. 202function \(matrix_in :: Array{BigFloat,2}, 203 vector_in :: Array{BigFloat,1}) 204 # Copy the inputs, because we'll be mutating them as we go. 205 M = copy(matrix_in) 206 V = copy(vector_in) 207 208 # Input consistency criteria: matrix is square, and vector has 209 # length to match. 210 n = length(V) 211 @assert(n > 0) 212 @assert(size(M) == (n,n)) 213 214 @debug("gausselim", "starting, n=", n) 215 216 for i = 1:1:n 217 # Straightforward Gaussian elimination: find the largest 218 # non-zero entry in column i (and in a row we haven't sorted 219 # out already), swap it into row i, scale that row to 220 # normalise it to 1, then zero out the rest of the column by 221 # subtracting a multiple of that row from each other row. 222 223 @debug("gausselim", "matrix=", repr(M)) 224 @debug("gausselim", "vector=", repr(V)) 225 226 # Find the best pivot. 227 bestrow = 0 228 bestval = 0 229 for j = i:1:n 230 if abs(M[j,i]) > bestval 231 bestrow = j 232 bestval = M[j,i] 233 end 234 end 235 @assert(bestrow > 0) # make sure we did actually find one 236 237 @debug("gausselim", "bestrow=", bestrow) 238 239 # Swap it into row i. 240 if bestrow != i 241 for k = 1:1:n 242 M[bestrow,k],M[i,k] = M[i,k],M[bestrow,k] 243 end 244 V[bestrow],V[i] = V[i],V[bestrow] 245 end 246 247 # Scale that row so that M[i,i] becomes 1. 248 divisor = M[i,i] 249 for k = 1:1:n 250 M[i,k] = M[i,k] / divisor 251 end 252 V[i] = V[i] / divisor 253 @assert(M[i,i] == 1) 254 255 # Zero out all other entries in column i, by subtracting 256 # multiples of this row. 257 for j = 1:1:n 258 if j != i 259 factor = M[j,i] 260 for k = 1:1:n 261 M[j,k] = M[j,k] - M[i,k] * factor 262 end 263 V[j] = V[j] - V[i] * factor 264 @assert(M[j,i] == 0) 265 end 266 end 267 end 268 269 @debug("gausselim", "matrix=", repr(M)) 270 @debug("gausselim", "vector=", repr(V)) 271 @debug("gausselim", "done!") 272 273 # Now we're done: M is the identity matrix, so the equation Mx=V 274 # becomes just x=V, i.e. V is already exactly the vector we want 275 # to return. 276 return V 277end 278 279# ---------------------------------------------------------------------- 280# Least-squares fitting of a rational function to a set of (x,y) 281# points. 282# 283# We use this to get an initial starting point for the Remez 284# iteration. Therefore, it doesn't really need to be particularly 285# accurate; it only needs to be good enough to wiggle back and forth 286# across the target function the right number of times (so as to give 287# enough error extrema to start optimising from) and not have any 288# poles in the target interval. 289# 290# Least-squares fitting of a _polynomial_ is actually a sensible thing 291# to do, and minimises the rms error. Doing the following trick with a 292# rational function P/Q is less sensible, because it cannot be made to 293# minimise the error function (P/Q-f)^2 that you actually wanted; 294# instead it minimises (P-fQ)^2. But that should be good enough to 295# have the properties described above. 296# 297# Some theory: suppose you're trying to choose a set of parameters a_i 298# so as to minimise the sum of squares of some error function E_i. 299# Basic calculus says, if you do this in one variable, just 300# differentiate and solve for zero. In this case, that works fine even 301# with multiple variables, because you _partially_ differentiate with 302# respect to each a_i, giving a system of equations, and that system 303# turns out to be linear so we just solve it as a matrix. 304# 305# In this case, our parameters are the coefficients of P and Q; to 306# avoid underdetermining the system we'll fix Q's constant term at 1, 307# so that our error function (as described above) is 308# 309# E = \sum (p_0 + p_1 x + ... + p_n x^n - y - y q_1 x - ... - y q_d x^d)^2 310# 311# where the sum is over all (x,y) coordinate pairs. Setting dE/dp_j=0 312# (for each j) gives an equation of the form 313# 314# 0 = \sum 2(p_0 + p_1 x + ... + p_n x^n - y - y q_1 x - ... - y q_d x^d) x^j 315# 316# and setting dE/dq_j=0 gives one of the form 317# 318# 0 = \sum 2(p_0 + p_1 x + ... + p_n x^n - y - y q_1 x - ... - y q_d x^d) y x^j 319# 320# And both of those row types, treated as multivariate linear 321# equations in the p,q values, have each coefficient being a value of 322# the form \sum x^i, \sum y x^i or \sum y^2 x^i, for various i. (Times 323# a factor of 2, but we can throw that away.) So we can go through the 324# list of input coordinates summing all of those things, and then we 325# have enough information to construct our matrix and solve it 326# straight off for the rational function coefficients. 327 328# Arguments: 329# f The function to be approximated. Maps BigFloat -> BigFloat. 330# xvals Array of BigFloats, giving the list of x-coordinates at which 331# to evaluate f. 332# n Degree of the numerator polynomial of the desired rational 333# function. 334# d Degree of the denominator polynomial of the desired rational 335# function. 336# w Error-weighting function. Takes two BigFloat arguments x,y 337# and returns a scaling factor for the error at that location. 338# A larger value indicates that the error should be given 339# greater weight in the square sum we try to minimise. 340# If unspecified, defaults to giving everything the same weight. 341# 342# Return values: a pair of arrays of BigFloats (N,D) giving the 343# coefficients of the returned rational function. N has size n+1; D 344# has size d+1. Both start with the constant term, i.e. N[i] is the 345# coefficient of x^(i-1) (because Julia arrays are 1-based). D[1] will 346# be 1. 347function ratfn_leastsquares(f::Function, xvals::Array{BigFloat}, n, d, 348 w = (x,y)->BigFloat(1)) 349 # Accumulate sums of x^i y^j, for j={0,1,2} and a range of x. 350 # Again because Julia arrays are 1-based, we'll have sums[i,j] 351 # being the sum of x^(i-1) y^(j-1). 352 maxpow = max(n,d) * 2 + 1 353 sums = zeros(BigFloat, maxpow, 3) 354 for x = xvals 355 y = f(x) 356 weight = w(x,y) 357 for i = 1:1:maxpow 358 for j = 1:1:3 359 sums[i,j] += x^(i-1) * y^(j-1) * weight 360 end 361 end 362 end 363 364 @debug("leastsquares", "sums=", repr(sums)) 365 366 # Build the matrix. We're solving n+d+1 equations in n+d+1 367 # unknowns. (We actually have to return n+d+2 coefficients, but 368 # one of them is hardwired to 1.) 369 matrix = array2d(BigFloat, n+d+1, n+d+1) 370 vector = array1d(BigFloat, n+d+1) 371 for i = 0:1:n 372 # Equation obtained by differentiating with respect to p_i, 373 # i.e. the numerator coefficient of x^i. 374 row = 1+i 375 for j = 0:1:n 376 matrix[row, 1+j] = sums[1+i+j, 1] 377 end 378 for j = 1:1:d 379 matrix[row, 1+n+j] = -sums[1+i+j, 2] 380 end 381 vector[row] = sums[1+i, 2] 382 end 383 for i = 1:1:d 384 # Equation obtained by differentiating with respect to q_i, 385 # i.e. the denominator coefficient of x^i. 386 row = 1+n+i 387 for j = 0:1:n 388 matrix[row, 1+j] = sums[1+i+j, 2] 389 end 390 for j = 1:1:d 391 matrix[row, 1+n+j] = -sums[1+i+j, 3] 392 end 393 vector[row] = sums[1+i, 3] 394 end 395 396 @debug("leastsquares", "matrix=", repr(matrix)) 397 @debug("leastsquares", "vector=", repr(vector)) 398 399 # Solve the matrix equation. 400 all_coeffs = matrix \ vector 401 402 @debug("leastsquares", "all_coeffs=", repr(all_coeffs)) 403 404 # And marshal the results into two separate polynomial vectors to 405 # return. 406 ncoeffs = all_coeffs[1:n+1] 407 dcoeffs = vcat([1], all_coeffs[n+2:n+d+1]) 408 return (ncoeffs, dcoeffs) 409end 410 411# ---------------------------------------------------------------------- 412# Golden-section search to find a maximum of a function. 413 414# Arguments: 415# f Function to be maximised/minimised. Maps BigFloat -> BigFloat. 416# a,b,c BigFloats bracketing a maximum of the function. 417# 418# Expects: 419# a,b,c are in order (either a<=b<=c or c<=b<=a) 420# a != c (but b can equal one or the other if it wants to) 421# f(a) <= f(b) >= f(c) 422# 423# Return value is an (x,y) pair of BigFloats giving the extremal input 424# and output. (That is, y=f(x).) 425function goldensection(f::Function, a::BigFloat, b::BigFloat, c::BigFloat) 426 # Decide on a 'good enough' threshold. 427 threshold = abs(c-a) * 2^(-epsbits/2) 428 429 # We'll need the golden ratio phi, of course. Or rather, in this 430 # case, we need 1/phi = 0.618... 431 one_over_phi = 2 / (1 + sqrt(BigFloat(5))) 432 433 # Flip round the interval endpoints so that the interval [a,b] is 434 # at least as large as [b,c]. (Then we can always pick our new 435 # point in [a,b] without having to handle lots of special cases.) 436 if abs(b-a) < abs(c-a) 437 a, c = c, a 438 end 439 440 # Evaluate the function at the initial points. 441 fa = f(a) 442 fb = f(b) 443 fc = f(c) 444 445 @debug("goldensection", "starting") 446 447 while abs(c-a) > threshold 448 @debug("goldensection", "a: ", a, " -> ", fa) 449 @debug("goldensection", "b: ", b, " -> ", fb) 450 @debug("goldensection", "c: ", c, " -> ", fc) 451 452 # Check invariants. 453 @assert(a <= b <= c || c <= b <= a) 454 @assert(fa <= fb >= fc) 455 456 # Subdivide the larger of the intervals [a,b] and [b,c]. We've 457 # arranged that this is always [a,b], for simplicity. 458 d = a + (b-a) * one_over_phi 459 460 # Now we have an interval looking like this (possibly 461 # reversed): 462 # 463 # a d b c 464 # 465 # and we know f(b) is bigger than either f(a) or f(c). We have 466 # two cases: either f(d) > f(b), or vice versa. In either 467 # case, we can narrow to an interval of 1/phi the size, and 468 # still satisfy all our invariants (three ordered points, 469 # [a,b] at least the width of [b,c], f(a)<=f(b)>=f(c)). 470 fd = f(d) 471 @debug("goldensection", "d: ", d, " -> ", fd) 472 if fd > fb 473 a, b, c = a, d, b 474 fa, fb, fc = fa, fd, fb 475 @debug("goldensection", "adb case") 476 else 477 a, b, c = c, b, d 478 fa, fb, fc = fc, fb, fd 479 @debug("goldensection", "cbd case") 480 end 481 end 482 483 @debug("goldensection", "done: ", b, " -> ", fb) 484 return (b, fb) 485end 486 487# ---------------------------------------------------------------------- 488# Find the extrema of a function within a given interval. 489 490# Arguments: 491# f The function to be approximated. Maps BigFloat -> BigFloat. 492# grid A set of points at which to evaluate f. Must be high enough 493# resolution to make extrema obvious. 494# 495# Returns an array of (x,y) pairs of BigFloats, with each x,y giving 496# the extremum location and its value (i.e. y=f(x)). 497function find_extrema(f::Function, grid::Array{BigFloat}) 498 len = length(grid) 499 extrema = array1d(Tuple{BigFloat, BigFloat}, 0) 500 for i = 1:1:len 501 # We have to provide goldensection() with three points 502 # bracketing the extremum. If the extremum is at one end of 503 # the interval, then the only way we can do that is to set two 504 # of the points equal (which goldensection() will cope with). 505 prev = max(1, i-1) 506 next = min(i+1, len) 507 508 # Find our three pairs of (x,y) coordinates. 509 xp, xi, xn = grid[prev], grid[i], grid[next] 510 yp, yi, yn = f(xp), f(xi), f(xn) 511 512 # See if they look like an extremum, and if so, ask 513 # goldensection() to give a more exact location for it. 514 if yp <= yi >= yn 515 push!(extrema, goldensection(f, xp, xi, xn)) 516 elseif yp >= yi <= yn 517 x, y = goldensection(x->-f(x), xp, xi, xn) 518 push!(extrema, (x, -y)) 519 end 520 end 521 return extrema 522end 523 524# ---------------------------------------------------------------------- 525# Winnow a list of a function's extrema to give a subsequence of a 526# specified length, with the extrema in the subsequence alternating 527# signs, and with the smallest absolute value of an extremum in the 528# subsequence as large as possible. 529# 530# We do this using a dynamic-programming approach. We work along the 531# provided array of extrema, and at all times, we track the best set 532# of extrema we have so far seen for each possible (length, sign of 533# last extremum) pair. Each new extremum is evaluated to see whether 534# it can be added to any previously seen best subsequence to make a 535# new subsequence that beats the previous record holder in its slot. 536 537# Arguments: 538# extrema An array of (x,y) pairs of BigFloats giving the input extrema. 539# n Number of extrema required as output. 540# 541# Returns a new array of (x,y) pairs which is a subsequence of the 542# original sequence. (So, in particular, if the input was sorted by x 543# then so will the output be.) 544function winnow_extrema(extrema::Array{Tuple{BigFloat,BigFloat}}, n) 545 # best[i,j] gives the best sequence so far of length i and with 546 # sign j (where signs are coded as 1=positive, 2=negative), in the 547 # form of a tuple (cost, actual array of x,y pairs). 548 best = fill((BigFloat(0), array1d(Tuple{BigFloat,BigFloat}, 0)), n, 2) 549 550 for (x,y) = extrema 551 if y > 0 552 sign = 1 553 elseif y < 0 554 sign = 2 555 else 556 # A zero-valued extremum cannot possibly contribute to any 557 # optimal sequence, so we simply ignore it! 558 continue 559 end 560 561 for i = 1:1:n 562 # See if we can create a new entry for best[i,sign] by 563 # appending our current (x,y) to some previous thing. 564 if i == 1 565 # Special case: we don't store a best zero-length 566 # sequence :-) 567 candidate = (abs(y), [(x,y)]) 568 else 569 othersign = 3-sign # map 1->2 and 2->1 570 oldscore, oldlist = best[i-1, othersign] 571 newscore = min(abs(y), oldscore) 572 newlist = vcat(oldlist, [(x,y)]) 573 candidate = (newscore, newlist) 574 end 575 # If our new candidate improves on the previous value of 576 # best[i,sign], then replace it. 577 if candidate[1] > best[i,sign][1] 578 best[i,sign] = candidate 579 end 580 end 581 end 582 583 # Our ultimate return value has to be either best[n,1] or 584 # best[n,2], but it could be either. See which one has the higher 585 # score. 586 if best[n,1][1] > best[n,2][1] 587 ret = best[n,1][2] 588 else 589 ret = best[n,2][2] 590 end 591 # Make sure we did actually _find_ a good answer. 592 @assert(length(ret) == n) 593 return ret 594end 595 596# ---------------------------------------------------------------------- 597# Construct a rational-function approximation with equal and 598# alternating weighted deviation at a specific set of x-coordinates. 599 600# Arguments: 601# f The function to be approximated. Maps BigFloat -> BigFloat. 602# coords An array of BigFloats giving the x-coordinates. There should 603# be n+d+2 of them. 604# n, d The degrees of the numerator and denominator of the desired 605# approximation. 606# prev_err A plausible value for the alternating weighted deviation. 607# (Required to kickstart a binary search in the nonlinear case; 608# see comments below.) 609# w Error-weighting function. Takes two BigFloat arguments x,y 610# and returns a scaling factor for the error at that location. 611# The returned approximation R should have the minimum possible 612# maximum value of abs((f(x)-R(x)) * w(x,f(x))). Optional 613# parameter, defaulting to the always-return-1 function. 614# 615# Return values: a pair of arrays of BigFloats (N,D) giving the 616# coefficients of the returned rational function. N has size n+1; D 617# has size d+1. Both start with the constant term, i.e. N[i] is the 618# coefficient of x^(i-1) (because Julia arrays are 1-based). D[1] will 619# be 1. 620function ratfn_equal_deviation(f::Function, coords::Array{BigFloat}, 621 n, d, prev_err::BigFloat, 622 w = (x,y)->BigFloat(1)) 623 @debug("equaldev", "n=", n, " d=", d, " coords=", repr(coords)) 624 @assert(length(coords) == n+d+2) 625 626 if d == 0 627 # Special case: we're after a polynomial. In this case, we 628 # have the particularly easy job of just constructing and 629 # solving a system of n+2 linear equations, to find the n+1 630 # coefficients of the polynomial and also the amount of 631 # deviation at the specified coordinates. Each equation is of 632 # the form 633 # 634 # p_0 x^0 + p_1 x^1 + ... + p_n x^n ± e/w(x) = f(x) 635 # 636 # in which the p_i and e are the variables, and the powers of 637 # x and calls to w and f are the coefficients. 638 639 matrix = array2d(BigFloat, n+2, n+2) 640 vector = array1d(BigFloat, n+2) 641 currsign = +1 642 for i = 1:1:n+2 643 x = coords[i] 644 for j = 0:1:n 645 matrix[i,1+j] = x^j 646 end 647 y = f(x) 648 vector[i] = y 649 matrix[i, n+2] = currsign / w(x,y) 650 currsign = -currsign 651 end 652 653 @debug("equaldev", "matrix=", repr(matrix)) 654 @debug("equaldev", "vector=", repr(vector)) 655 656 outvector = matrix \ vector 657 658 @debug("equaldev", "outvector=", repr(outvector)) 659 660 ncoeffs = outvector[1:n+1] 661 dcoeffs = [BigFloat(1)] 662 return ncoeffs, dcoeffs 663 else 664 # For a nontrivial rational function, the system of equations 665 # we need to solve becomes nonlinear, because each equation 666 # now takes the form 667 # 668 # p_0 x^0 + p_1 x^1 + ... + p_n x^n 669 # --------------------------------- ± e/w(x) = f(x) 670 # x^0 + q_1 x^1 + ... + q_d x^d 671 # 672 # and multiplying up by the denominator gives you a lot of 673 # terms containing e × q_i. So we can't do this the really 674 # easy way using a matrix equation as above. 675 # 676 # Fortunately, this is a fairly easy kind of nonlinear system. 677 # The equations all become linear if you switch to treating e 678 # as a constant, so a reasonably sensible approach is to pick 679 # a candidate value of e, solve all but one of the equations 680 # for the remaining unknowns, and then see what the error 681 # turns out to be in the final equation. The Chebyshev 682 # alternation theorem guarantees that that error in the last 683 # equation will be anti-monotonic in the input e, so we can 684 # just binary-search until we get the two as close to equal as 685 # we need them. 686 687 function try_e(e) 688 # Try a given value of e, derive the coefficients of the 689 # resulting rational function by setting up equations 690 # based on the first n+d+1 of the n+d+2 coordinates, and 691 # see what the error turns out to be at the final 692 # coordinate. 693 matrix = array2d(BigFloat, n+d+1, n+d+1) 694 vector = array1d(BigFloat, n+d+1) 695 currsign = +1 696 for i = 1:1:n+d+1 697 x = coords[i] 698 y = f(x) 699 y_adj = y - currsign * e / w(x,y) 700 for j = 0:1:n 701 matrix[i,1+j] = x^j 702 end 703 for j = 1:1:d 704 matrix[i,1+n+j] = -x^j * y_adj 705 end 706 vector[i] = y_adj 707 currsign = -currsign 708 end 709 710 @debug("equaldev", "trying e=", e) 711 @debug("equaldev", "matrix=", repr(matrix)) 712 @debug("equaldev", "vector=", repr(vector)) 713 714 outvector = matrix \ vector 715 716 @debug("equaldev", "outvector=", repr(outvector)) 717 718 ncoeffs = outvector[1:n+1] 719 dcoeffs = vcat([BigFloat(1)], outvector[n+2:n+d+1]) 720 721 x = coords[n+d+2] 722 y = f(x) 723 last_e = (ratfn_eval(ncoeffs, dcoeffs, x) - y) * w(x,y) * -currsign 724 725 @debug("equaldev", "last e=", last_e) 726 727 return ncoeffs, dcoeffs, last_e 728 end 729 730 threshold = 2^(-epsbits/2) # convergence threshold 731 732 # Start by trying our previous iteration's error value. This 733 # value (e0) will be one end of our binary-search interval, 734 # and whatever it caused the last point's error to be, that 735 # (e1) will be the other end. 736 e0 = prev_err 737 @debug("equaldev", "e0 = ", e0) 738 nc, dc, e1 = try_e(e0) 739 @debug("equaldev", "e1 = ", e1) 740 if abs(e1-e0) <= threshold 741 # If we're _really_ lucky, we hit the error right on the 742 # nose just by doing that! 743 return nc, dc 744 end 745 s = sign(e1-e0) 746 @debug("equaldev", "s = ", s) 747 748 # Verify by assertion that trying our other interval endpoint 749 # e1 gives a value that's wrong in the other direction. 750 # (Otherwise our binary search won't get a sensible answer at 751 # all.) 752 nc, dc, e2 = try_e(e1) 753 @debug("equaldev", "e2 = ", e2) 754 @assert(sign(e2-e1) == -s) 755 756 # Now binary-search until our two endpoints narrow enough. 757 local emid 758 while abs(e1-e0) > threshold 759 emid = (e1+e0)/2 760 nc, dc, enew = try_e(emid) 761 if sign(enew-emid) == s 762 e0 = emid 763 else 764 e1 = emid 765 end 766 end 767 768 @debug("equaldev", "final e=", emid) 769 return nc, dc 770 end 771end 772 773# ---------------------------------------------------------------------- 774# Top-level function to find a minimax rational-function approximation. 775 776# Arguments: 777# f The function to be approximated. Maps BigFloat -> BigFloat. 778# interval A pair of BigFloats giving the endpoints of the interval 779# (in either order) on which to approximate f. 780# n, d The degrees of the numerator and denominator of the desired 781# approximation. 782# w Error-weighting function. Takes two BigFloat arguments x,y 783# and returns a scaling factor for the error at that location. 784# The returned approximation R should have the minimum possible 785# maximum value of abs((f(x)-R(x)) * w(x,f(x))). Optional 786# parameter, defaulting to the always-return-1 function. 787# 788# Return values: a tuple (N,D,E,X), where 789 790# N,D A pair of arrays of BigFloats giving the coefficients 791# of the returned rational function. N has size n+1; D 792# has size d+1. Both start with the constant term, i.e. 793# N[i] is the coefficient of x^(i-1) (because Julia 794# arrays are 1-based). D[1] will be 1. 795# E The maximum weighted error (BigFloat). 796# X An array of pairs of BigFloats giving the locations of n+2 797# points and the weighted error at each of those points. The 798# weighted error values will have alternating signs, which 799# means that the Chebyshev alternation theorem guarantees 800# that any other function of the same degree must exceed 801# the error of this one at at least one of those points. 802function ratfn_minimax(f::Function, interval::Tuple{BigFloat,BigFloat}, n, d, 803 w = (x,y)->BigFloat(1)) 804 # We start off by finding a least-squares approximation. This 805 # doesn't need to be perfect, but if we can get it reasonably good 806 # then it'll save iterations in the refining stage. 807 # 808 # Least-squares approximations tend to look nicer in a minimax 809 # sense if you evaluate the function at a big pile of Chebyshev 810 # nodes rather than uniformly spaced points. These values will 811 # also make a good grid to use for the initial search for error 812 # extrema, so we'll keep them around for that reason too. 813 814 # Construct the grid. 815 lo, hi = minimum(interval), maximum(interval) 816 local grid 817 let 818 mid = (hi+lo)/2 819 halfwid = (hi-lo)/2 820 nnodes = 16 * (n+d+1) 821 pi = 2*asin(BigFloat(1)) 822 grid = [ mid - halfwid * cos(pi*i/nnodes) for i=0:1:nnodes ] 823 end 824 825 # Find the initial least-squares approximation. 826 (nc, dc) = ratfn_leastsquares(f, grid, n, d, w) 827 @debug("minimax", "initial leastsquares approx = ", 828 ratfn_to_string(nc, dc)) 829 830 # Threshold of convergence. We stop when the relative difference 831 # between the min and max (winnowed) error extrema is less than 832 # this. 833 # 834 # This is set to the cube root of machine epsilon on a more or 835 # less empirical basis, because the rational-function case will 836 # not converge reliably if you set it to only the square root. 837 # (Repeatable by using the --test mode.) On the assumption that 838 # input and output error in each iteration can be expected to be 839 # related by a simple power law (because it'll just be down to how 840 # many leading terms of a Taylor series are zero), the cube root 841 # was the next thing to try. 842 threshold = 2^(-epsbits/3) 843 844 # Main loop. 845 while true 846 # Find all the error extrema we can. 847 function compute_error(x) 848 real_y = f(x) 849 approx_y = ratfn_eval(nc, dc, x) 850 return (approx_y - real_y) * w(x, real_y) 851 end 852 extrema = find_extrema(compute_error, grid) 853 @debug("minimax", "all extrema = ", format_xylist(extrema)) 854 855 # Winnow the extrema down to the right number, and ensure they 856 # have alternating sign. 857 extrema = winnow_extrema(extrema, n+d+2) 858 @debug("minimax", "winnowed extrema = ", format_xylist(extrema)) 859 860 # See if we've finished. 861 min_err = minimum([abs(y) for (x,y) = extrema]) 862 max_err = maximum([abs(y) for (x,y) = extrema]) 863 variation = (max_err - min_err) / max_err 864 @debug("minimax", "extremum variation = ", variation) 865 if variation < threshold 866 @debug("minimax", "done!") 867 return nc, dc, max_err, extrema 868 end 869 870 # If not, refine our function by equalising the error at the 871 # extrema points, and go round again. 872 (nc, dc) = ratfn_equal_deviation(f, map(x->x[1], extrema), 873 n, d, max_err, w) 874 @debug("minimax", "refined approx = ", ratfn_to_string(nc, dc)) 875 end 876end 877 878# ---------------------------------------------------------------------- 879# Check if a polynomial is well-conditioned for accurate evaluation in 880# a given interval by Horner's rule. 881# 882# This is true if at every step where Horner's rule computes 883# (coefficient + x*value_so_far), the constant coefficient you're 884# adding on is of larger magnitude than the x*value_so_far operand. 885# And this has to be true for every x in the interval. 886# 887# Arguments: 888# coeffs The coefficients of the polynomial under test. Starts with 889# the constant term, i.e. coeffs[i] is the coefficient of 890# x^(i-1) (because Julia arrays are 1-based). 891# lo, hi The bounds of the interval. 892# 893# Return value: the largest ratio (x*value_so_far / coefficient), at 894# any step of evaluation, for any x in the interval. If this is less 895# than 1, the polynomial is at least somewhat well-conditioned; 896# ideally you want it to be more like 1/8 or 1/16 or so, so that the 897# relative rounding error accumulated at each step are reduced by 898# several factors of 2 when the next coefficient is added on. 899 900function wellcond(coeffs, lo, hi) 901 x = max(abs(lo), abs(hi)) 902 worst = 0 903 so_far = 0 904 for i = length(coeffs):-1:1 905 coeff = abs(coeffs[i]) 906 so_far *= x 907 if coeff != 0 908 thisval = so_far / coeff 909 worst = max(worst, thisval) 910 so_far += coeff 911 end 912 end 913 return worst 914end 915 916# ---------------------------------------------------------------------- 917# Small set of unit tests. 918 919function test() 920 passes = 0 921 fails = 0 922 923 function approx_eq(x, y, limit=1e-6) 924 return abs(x - y) < limit 925 end 926 927 function test(condition) 928 if condition 929 passes += 1 930 else 931 println("fail") 932 fails += 1 933 end 934 end 935 936 # Test Gaussian elimination. 937 println("Gaussian test 1:") 938 m = BigFloat[1 1 2; 3 5 8; 13 34 21] 939 v = BigFloat[1, -1, 2] 940 ret = m \ v 941 println(" ",repr(ret)) 942 test(approx_eq(ret[1], 109/26)) 943 test(approx_eq(ret[2], -105/130)) 944 test(approx_eq(ret[3], -31/26)) 945 946 # Test leastsquares rational functions. 947 println("Leastsquares test 1:") 948 n = 10000 949 a = array1d(BigFloat, n+1) 950 for i = 0:1:n 951 a[1+i] = i/BigFloat(n) 952 end 953 (nc, dc) = ratfn_leastsquares(x->exp(x), a, 2, 2) 954 println(" ",ratfn_to_string(nc, dc)) 955 for x = a 956 test(approx_eq(exp(x), ratfn_eval(nc, dc, x), 1e-4)) 957 end 958 959 # Test golden section search. 960 println("Golden section test 1:") 961 x, y = goldensection(x->sin(x), 962 BigFloat(0), BigFloat(1)/10, BigFloat(4)) 963 println(" ", x, " -> ", y) 964 test(approx_eq(x, asin(BigFloat(1)))) 965 test(approx_eq(y, 1)) 966 967 # Test extrema-winnowing algorithm. 968 println("Winnow test 1:") 969 extrema = [(x, sin(20*x)*sin(197*x)) 970 for x in BigFloat(0):BigFloat(1)/1000:BigFloat(1)] 971 winnowed = winnow_extrema(extrema, 12) 972 println(" ret = ", format_xylist(winnowed)) 973 prevx, prevy = -1, 0 974 for (x,y) = winnowed 975 test(x > prevx) 976 test(y != 0) 977 test(prevy * y <= 0) # tolerates initial prevx having no sign 978 test(abs(y) > 0.9) 979 prevx, prevy = x, y 980 end 981 982 # Test actual minimax approximation. 983 println("Minimax test 1 (polynomial):") 984 (nc, dc, e, x) = ratfn_minimax(x->exp(x), (BigFloat(0), BigFloat(1)), 4, 0) 985 println(" ",e) 986 println(" ",ratfn_to_string(nc, dc)) 987 test(0 < e < 1e-3) 988 for x = 0:BigFloat(1)/1000:1 989 test(abs(ratfn_eval(nc, dc, x) - exp(x)) <= e * 1.0000001) 990 end 991 992 println("Minimax test 2 (rational):") 993 (nc, dc, e, x) = ratfn_minimax(x->exp(x), (BigFloat(0), BigFloat(1)), 2, 2) 994 println(" ",e) 995 println(" ",ratfn_to_string(nc, dc)) 996 test(0 < e < 1e-3) 997 for x = 0:BigFloat(1)/1000:1 998 test(abs(ratfn_eval(nc, dc, x) - exp(x)) <= e * 1.0000001) 999 end 1000 1001 println("Minimax test 3 (polynomial, weighted):") 1002 (nc, dc, e, x) = ratfn_minimax(x->exp(x), (BigFloat(0), BigFloat(1)), 4, 0, 1003 (x,y)->1/y) 1004 println(" ",e) 1005 println(" ",ratfn_to_string(nc, dc)) 1006 test(0 < e < 1e-3) 1007 for x = 0:BigFloat(1)/1000:1 1008 test(abs(ratfn_eval(nc, dc, x) - exp(x))/exp(x) <= e * 1.0000001) 1009 end 1010 1011 println("Minimax test 4 (rational, weighted):") 1012 (nc, dc, e, x) = ratfn_minimax(x->exp(x), (BigFloat(0), BigFloat(1)), 2, 2, 1013 (x,y)->1/y) 1014 println(" ",e) 1015 println(" ",ratfn_to_string(nc, dc)) 1016 test(0 < e < 1e-3) 1017 for x = 0:BigFloat(1)/1000:1 1018 test(abs(ratfn_eval(nc, dc, x) - exp(x))/exp(x) <= e * 1.0000001) 1019 end 1020 1021 println("Minimax test 5 (rational, weighted, odd degree):") 1022 (nc, dc, e, x) = ratfn_minimax(x->exp(x), (BigFloat(0), BigFloat(1)), 2, 1, 1023 (x,y)->1/y) 1024 println(" ",e) 1025 println(" ",ratfn_to_string(nc, dc)) 1026 test(0 < e < 1e-3) 1027 for x = 0:BigFloat(1)/1000:1 1028 test(abs(ratfn_eval(nc, dc, x) - exp(x))/exp(x) <= e * 1.0000001) 1029 end 1030 1031 total = passes + fails 1032 println(passes, " passes ", fails, " fails ", total, " total") 1033end 1034 1035# ---------------------------------------------------------------------- 1036# Online help. 1037function help() 1038 print(""" 1039Usage: 1040 1041 remez.jl [options] <lo> <hi> <n> <d> <expr> [<weight>] 1042 1043Arguments: 1044 1045 <lo>, <hi> 1046 1047 Bounds of the interval on which to approximate the target 1048 function. These are parsed and evaluated as Julia expressions, 1049 so you can write things like '1/BigFloat(6)' to get an 1050 accurate representation of 1/6, or '4*atan(BigFloat(1))' to 1051 get pi. (Unfortunately, the obvious 'BigFloat(pi)' doesn't 1052 work in Julia.) 1053 1054 <n>, <d> 1055 1056 The desired degree of polynomial(s) you want for your 1057 approximation. These should be non-negative integers. If you 1058 want a rational function as output, set <n> to the degree of 1059 the numerator, and <d> the denominator. If you just want an 1060 ordinary polynomial, set <d> to 0, and <n> to the degree of 1061 the polynomial you want. 1062 1063 <expr> 1064 1065 A Julia expression giving the function to be approximated on 1066 the interval. The input value is predefined as 'x' when this 1067 expression is evaluated, so you should write something along 1068 the lines of 'sin(x)' or 'sqrt(1+tan(x)^2)' etc. 1069 1070 <weight> 1071 1072 If provided, a Julia expression giving the weighting factor 1073 for the approximation error. The output polynomial will 1074 minimise the largest absolute value of (P-f) * w at any point 1075 in the interval, where P is the value of the polynomial, f is 1076 the value of the target function given by <expr>, and w is the 1077 weight given by this function. 1078 1079 When this expression is evaluated, the input value to P and f 1080 is predefined as 'x', and also the true output value f(x) is 1081 predefined as 'y'. So you can minimise the relative error by 1082 simply writing '1/y'. 1083 1084 If the <weight> argument is not provided, the default 1085 weighting function always returns 1, so that the polynomial 1086 will minimise the maximum absolute error |P-f|. 1087 1088Computation options: 1089 1090 --pre=<predef_expr> 1091 1092 Evaluate the Julia expression <predef_expr> before starting 1093 the computation. This permits you to pre-define variables or 1094 functions which the Julia expressions in your main arguments 1095 can refer to. All of <lo>, <hi>, <expr> and <weight> can make 1096 use of things defined by <predef_expr>. 1097 1098 One internal remez.jl function that you might sometimes find 1099 useful in this expression is 'goldensection', which finds the 1100 location and value of a maximum of a function. For example, 1101 one implementation strategy for the gamma function involves 1102 translating it to put its unique local minimum at the origin, 1103 in which case you can write something like this 1104 1105 --pre='(m,my) = goldensection(x -> -gamma(x), 1106 BigFloat(1), BigFloat(1.5), BigFloat(2))' 1107 1108 to predefine 'm' as the location of gamma's minimum, and 'my' 1109 as the (negated) value that gamma actually takes at that 1110 point, i.e. -gamma(m). 1111 1112 (Since 'goldensection' always finds a maximum, we had to 1113 negate gamma in the input function to make it find a minimum 1114 instead. Consult the comments in the source for more details 1115 on the use of this function.) 1116 1117 If you use this option more than once, all the expressions you 1118 provide will be run in sequence. 1119 1120 --bits=<bits> 1121 1122 Specify the accuracy to which you want the output polynomial, 1123 in bits. Default 256, which should be more than enough. 1124 1125 --bigfloatbits=<bits> 1126 1127 Turn up the precision used by Julia for its BigFloat 1128 evaluation. Default is Julia's default (also 256). You might 1129 want to try setting this higher than the --bits value if the 1130 algorithm is failing to converge for some reason. 1131 1132Output options: 1133 1134 --full 1135 1136 Instead of just printing the approximation function itself, 1137 also print auxiliary information: 1138 - the locations of the error extrema, and the actual 1139 (weighted) error at each of those locations 1140 - the overall maximum error of the function 1141 - a 'well-conditioning quotient', giving the worst-case ratio 1142 between any polynomial coefficient and the largest possible 1143 value of the higher-order terms it will be added to. 1144 1145 The well-conditioning quotient should be less than 1, ideally 1146 by several factors of two, for accurate evaluation in the 1147 target precision. If you request a rational function, a 1148 separate well-conditioning quotient will be printed for the 1149 numerator and denominator. 1150 1151 Use this option when deciding how wide an interval to 1152 approximate your function on, and what degree of polynomial 1153 you need. 1154 1155 --variable=<identifier> 1156 1157 When writing the output polynomial or rational function in its 1158 usual form as an arithmetic expression, use <identifier> as 1159 the name of the input variable. Default is 'x'. 1160 1161 --suffix=<suffix> 1162 1163 When writing the output polynomial or rational function in its 1164 usual form as an arithmetic expression, write <suffix> after 1165 every floating-point literal. For example, '--suffix=F' will 1166 generate a C expression in which the coefficients are literals 1167 of type 'float' rather than 'double'. 1168 1169 --array 1170 1171 Instead of writing the output polynomial as an arithmetic 1172 expression in Horner's rule form, write out just its 1173 coefficients, one per line, each with a trailing comma. 1174 Suitable for pasting into a C array declaration. 1175 1176 This option is not currently supported if the output is a 1177 rational function, because you'd need two separate arrays for 1178 the numerator and denominator coefficients and there's no 1179 obviously right way to provide both of those together. 1180 1181Debug and test options: 1182 1183 --debug=<facility> 1184 1185 Enable debugging output from various parts of the Remez 1186 calculation. <facility> should be the name of one of the 1187 classes of diagnostic output implemented in the program. 1188 Useful values include 'gausselim', 'leastsquares', 1189 'goldensection', 'equaldev', 'minimax'. This is probably 1190 mostly useful to people debugging problems with the script, so 1191 consult the source code for more information about what the 1192 diagnostic output for each of those facilities will be. 1193 1194 If you want diagnostics from more than one facility, specify 1195 this option multiple times with different arguments. 1196 1197 --test 1198 1199 Run remez.jl's internal test suite. No arguments needed. 1200 1201Miscellaneous options: 1202 1203 --help 1204 1205 Display this text and exit. No arguments needed. 1206 1207""") 1208end 1209 1210# ---------------------------------------------------------------------- 1211# Main program. 1212 1213function main() 1214 nargs = length(argwords) 1215 if nargs != 5 && nargs != 6 1216 error("usage: remez.jl <lo> <hi> <n> <d> <expr> [<weight>]\n" * 1217 " run 'remez.jl --help' for more help") 1218 end 1219 1220 for preliminary_command in preliminary_commands 1221 eval(Meta.parse(preliminary_command)) 1222 end 1223 1224 lo = BigFloat(eval(Meta.parse(argwords[1]))) 1225 hi = BigFloat(eval(Meta.parse(argwords[2]))) 1226 n = parse(Int,argwords[3]) 1227 d = parse(Int,argwords[4]) 1228 f = eval(Meta.parse("x -> " * argwords[5])) 1229 1230 # Wrap the user-provided function with a function of our own. This 1231 # arranges to detect silly FP values (inf,nan) early and diagnose 1232 # them sensibly, and also lets us log all evaluations of the 1233 # function in case you suspect it's doing the wrong thing at some 1234 # special-case point. 1235 function func(x) 1236 y = run(f,x) 1237 @debug("f", x, " -> ", y) 1238 if !isfinite(y) 1239 error("f(" * string(x) * ") returned non-finite value " * string(y)) 1240 end 1241 return y 1242 end 1243 1244 if nargs == 6 1245 # Wrap the user-provided weight function similarly. 1246 w = eval(Meta.parse("(x,y) -> " * argwords[6])) 1247 function wrapped_weight(x,y) 1248 ww = run(w,x,y) 1249 if !isfinite(ww) 1250 error("w(" * string(x) * "," * string(y) * 1251 ") returned non-finite value " * string(ww)) 1252 end 1253 return ww 1254 end 1255 weight = wrapped_weight 1256 else 1257 weight = (x,y)->BigFloat(1) 1258 end 1259 1260 (nc, dc, e, extrema) = ratfn_minimax(func, (lo, hi), n, d, weight) 1261 if array_format 1262 if d == 0 1263 functext = join([string(x)*",\n" for x=nc],"") 1264 else 1265 # It's unclear how you should best format an array of 1266 # coefficients for a rational function, so I'll leave 1267 # implementing this option until I have a use case. 1268 error("--array unsupported for rational functions") 1269 end 1270 else 1271 functext = ratfn_to_string(nc, dc) * "\n" 1272 end 1273 if full_output 1274 # Print everything you might want to know about the function 1275 println("extrema = ", format_xylist(extrema)) 1276 println("maxerror = ", string(e)) 1277 if length(dc) > 1 1278 println("wellconditioning_numerator = ", 1279 string(wellcond(nc, lo, hi))) 1280 println("wellconditioning_denominator = ", 1281 string(wellcond(dc, lo, hi))) 1282 else 1283 println("wellconditioning = ", string(wellcond(nc, lo, hi))) 1284 end 1285 print("function = ", functext) 1286 else 1287 # Just print the text people will want to paste into their code 1288 print(functext) 1289 end 1290end 1291 1292# ---------------------------------------------------------------------- 1293# Top-level code: parse the argument list and decide what to do. 1294 1295what_to_do = main 1296 1297doing_opts = true 1298argwords = array1d(String, 0) 1299for arg = ARGS 1300 global doing_opts, what_to_do, argwords 1301 global full_output, array_format, xvarname, floatsuffix, epsbits 1302 if doing_opts && startswith(arg, "-") 1303 if arg == "--" 1304 doing_opts = false 1305 elseif arg == "--help" 1306 what_to_do = help 1307 elseif arg == "--test" 1308 what_to_do = test 1309 elseif arg == "--full" 1310 full_output = true 1311 elseif arg == "--array" 1312 array_format = true 1313 elseif startswith(arg, "--debug=") 1314 enable_debug(arg[length("--debug=")+1:end]) 1315 elseif startswith(arg, "--variable=") 1316 xvarname = arg[length("--variable=")+1:end] 1317 elseif startswith(arg, "--suffix=") 1318 floatsuffix = arg[length("--suffix=")+1:end] 1319 elseif startswith(arg, "--bits=") 1320 epsbits = parse(Int,arg[length("--bits=")+1:end]) 1321 elseif startswith(arg, "--bigfloatbits=") 1322 set_bigfloat_precision( 1323 parse(Int,arg[length("--bigfloatbits=")+1:end])) 1324 elseif startswith(arg, "--pre=") 1325 push!(preliminary_commands, arg[length("--pre=")+1:end]) 1326 else 1327 error("unrecognised option: ", arg) 1328 end 1329 else 1330 push!(argwords, arg) 1331 end 1332end 1333 1334what_to_do() 1335