supercollider / supercollider

An audio server, programming language, and IDE for sound synthesis and algorithmic composition.
http://supercollider.github.io
GNU General Public License v3.0
5.52k stars 750 forks source link

FR: equivalent to printf / sprintf #3570

Open aspiers opened 6 years ago

aspiers commented 6 years ago

I'm having a real struggle trying to achieve the equivalent of a typical printf with something like %-5.3f, or even %.3f, and I'm not the only one.

String#-format only allows basic interpolation, and even with things like padRight and asStringPrec this is way too hard. It's a must-have core feature for any language really, so it'd be great if it was added to sclang.

mossheim commented 6 years ago

Yep, this is definitely something I've wanted and I've seen multiple requests for it. We should implement it and consider deprecating asStringPrec in the process.

mkb218 commented 6 years ago

There's an undocumented fformat method on String which looks like it may have been intended for this.

https://github.com/supercollider/supercollider/commit/0ff292a7ec7cad871a604654f64cf24e98e63592

mkb218 commented 6 years ago

I started work on this here https://github.com/mkb218/supercollider/tree/topic-printf

mossheim commented 6 years ago

@mkb218 Thanks for the effort! But it should really be done as a C++ primitive, so that we don't reinvent the wheel while introducing new bugs. There is information on writing primitives in the SC help system - http://doc.sccode.org/Guides/WritingPrimitives.html. You could also take a look at the existing string primitives for ideas; the new code will probably go there (https://github.com/supercollider/supercollider/blob/develop/lang/LangPrimSource/PyrStringPrim.cpp).

beatboxchad commented 5 years ago

hey @mkb218 I'm tired of not being a marketable C++ dev and this looks like an afternoon's baby step toward fixing that for me. Are you in the middle of cooking something per the above suggestion, or do you mind if I take a shot?

mkb218 commented 5 years ago

hi @beatboxchad I will likely not have time to accomplish this one until October. Go for it.

beatboxchad commented 5 years ago

Just an update: I was able to understand the primitive system relatively quickly, and began coding a solution. But in a dev meeting we discussed the bigger picture of the various string formatting facilities in sclang and it was suggested that this issue in context with others was a good initial candidate for an RFC, a process introduced right around the same time.

Day job and mental health stuff has interfered with my ability to volunteer the time needed to research the RFC, but I intend to catch a break and finish this by the end of the year.

mnvr commented 1 year ago

Might help someone meanwhile - a sclang implementation of a sibling to asStringPrec that uses the "%f" specifier

(
// The default asStringPrec uses "%g". This method allows us to instead
// use a "%f" style output.
//
// Example:
//
//  ~asStringPrecF.(-2.08008714253083e-07) == "-0.000000208008714"
//
~asStringPrecF = { |f, prec = 14|
    var as, c, pre;
    as = f.abs.asString;
    // Pass through values that don't have an negative exponent
    if (as.containsi("e-").not) { f.asStringPrec(prec) } {
        // Otherwise split on the exponent
        c = as.split($e);
        c[0].remove($.);
        pre = if(f < 0, "-0.", "0.");
        [pre,
            "0".extend(c[1].asInteger.neg - 1, $0),
            c[0]].join[..(prec + pre.size - 1)]
    }
};

~test_asStringPrecF = {
    var t, rt;
    t = UnitTest.new;
    UnitTest.reportPasses = false;
    t.assertEquals(
        ~asStringPrecF.( 2.08008714253083e-07),
        "0.00000020800871");
    t.assertEquals(
        ~asStringPrecF.(-2.08008714253083e-07),
        "-0.00000020800871");
    t.assertEquals(
        ~asStringPrecF.(9.58003511186689e-07),
        "0.00000095800351");

    100.do {
        rt = 0.5e-3.rand2;
        t.assertFloatEquals(~asStringPrecF.(rt).asFloat, rt)
    };
 };

~test_asStringPrecF.value();
)

Also packaged as a method, if that's more useful:


+ Float {
    // The default asStringPrec uses "%g". This method allows us to instead
    // use a "%f" style output.
    //
    // Example:
    //
    //  asStringPrecF(-2.08008714253083e-07) == "-0.000000208008714"
    //
    asStringPrecF { |prec = 14|
        var as, c, pre;
        as = this.abs.asString;
        // Pass through values that don't have an negative exponent
        if (as.containsi("e-").not) { ^this.asStringPrec(prec) } {
            // Otherwise split on the exponent
            c = as.split($e);
            c[0].remove($.);
            pre = if(this < 0, "-0.", "0.");
            ^[pre,
                "0".extend(c[1].asInteger.neg - 1, $0),
                c[0]].join[..(prec + pre.size - 1)]
        }
    }
}