Float to Q1.15: A FizzBuzz for Audio Technical Interview

Posted on Sun 19 January 2025 in Programming

I don’t enjoy watching candidates squirm through tricky interviews any more than anyone else does. Let’s face it: a job interview is stressful enough! Being judged on every syllable over a video call (or in a room) isn’t exactly fun. And if I’m the interviewer, I’m also under pressure to complete my own work on top of conducting the interview.

It’s counterproductive to throw overly difficult questions at candidates just to see them fail. Instead, I like straightforward “benchmark” questions—much like FizzBuzz. FizzBuzz is trivial to solve in under five minutes, but it still offers a ton of room for improvement and optimization. Don’t believe me? Check out the High throughput FizzBuzz code golf page, where some solutions run nearly as fast as memcpy(3)!

I wanted a similar litmus test for hiring audio programming candidates, and I think I may have found it. Let’s dive into the “Float to Q1.16” challenge: The FizzBuzz of audio programming:

Task: Convert an array of 32-bit floating-point audio samples into 16-bit fixed-point format (Q1.15). Specifically:

  1. Input Range: Any valid float32 value (i.e., the entire representable range of single-precision floats).
  2. Output Range: Scale, round, and clamp so that [-1.0, +1.0) in floating-point maps to [-32768, +32767) in int16_t. Any values below -1.0 should clamp to -32768, and any values above +1.0 should clamp to +32767.

A suitable function prototype might be:

void float_to_q1x15(int16_t *dst, const float *src, size_t samples);

Here, I will also give a possible answer as well:

void float_to_q1x15(int16_t *dst, const float *src, size_t len) {
  for (size_t i = 0; i < len; i++) {
    float x = src[i] * 0x1.p15F; /* Scale by 2**15 */

    /* Clamp */
    if (x > INT16_MAX) x = INT16_MAX;
    if (x < INT16_MIN) x = INT16_MIN;

    dst[i] = (int16_t)x; /* Convert to int16_t */
  }
}

Easy, right? But this is just the start. FizzBuzz is also easy—until it’s not. Let’s expand on the requirements:

  • What if the input is 0.5? What output do we get?
  • What if the input is 0.50001 or 0.49995? How do we handle rounding?
  • Are we applying simple truncation, or do we need a specific rounding method (e.g., round half up, round half to even, or dither)?

Now we’re dipping our toes into DSP theory and the quirks of floating-point arithmetic. Floats are straightforward to use in code but notoriously tricky to get fully “correct.” It’s not uncommon to encounter a mysterious NaN that sneaks into your audio pipeline because of a filter that’s been pushed just a bit too far. From my experience as an audio software engineer, I can tell you that NaNs are like nuclear bombs for an audio pipeline, and no amount of -ffast-math will save you from the fallout.

Questions to consider next:

  • Given [-1, +1) as your input range, how many bits of precision do you truly have in float32 representation?
  • Should you always round your scaled output, or is truncation acceptable?
  • If rounding, which method is best for audio signals?

By exploring these nuances, you can quickly spot which candidates have hands-on DSP experience—and which ones haven’t battled those late-night NaN bugs (yet). This is exactly why I like to call this “the FizzBuzz of audio programming.” It starts simple but reveals deeper topics lurking just below the surface. Plus, I think it's a fun way to continue a conversation!