audioconvert: introduce s25_32 type, f32<->s25 cast is lossless

The largest integer that 32-bit floating point can exactly represent
is actually `(2^24)-1`, not`(2^23)-1` like the code assumes.
This means, whenever we use s24 as an intermediate step
to go between f32 and s32, we lose a bit of precision.

s25_32 is really a i32 with highest byte always being a sign byte.

Printing was done by adding
```
for(int e = 0; e != 13; ++e)
fprintf(stderr, "%16.32e,", ((float*)m1)[e]);
```
to `compare_mem`. I don't like how these tests work.

https://godbolt.org/z/abe94sedT
This commit is contained in:
Roman Lebedev 2024-06-13 16:18:33 +03:00
parent 7b4c0dd5ec
commit 2a035ac49e
No known key found for this signature in database
GPG Key ID: 083C3EBB4A1689E0
2 changed files with 131 additions and 9 deletions

View File

@ -88,6 +88,18 @@
#define U32_TO_U24_32(v) (((uint32_t)(v)) >> 8)
#define S25_MIN -16777216
#define S25_MAX 16777215
#define S25_SCALE 16777216.0f
#define S25_32_TO_F32(v) ITOF(int32_t, v, S25_SCALE, 0.0f)
#define S25_32S_TO_F32(v) S25_32_TO_F32(bswap_32(v))
#define F32_TO_S25_32_D(v,d) FTOI(int32_t, v, S25_SCALE, 0.0f, d, S25_MIN, S25_MAX)
#define F32_TO_S25_32(v) F32_TO_S25_32_D(v, 0.0f)
#define F32_TO_S25_32S(v) bswap_32(F32_TO_S25_32(v))
#define F32_TO_S25_32S_D(v,d) bswap_32(F32_TO_S25_32_D(v,d))
#define S25_32_TO_S32(v) ((int32_t)(((uint32_t)(v)) << 7))
#define S32_TO_S25_32(v) (((int32_t)(v)) >> 7)
#define U32_MIN 0u
#define U32_MAX 4294967295u
#define U32_SCALE 2147483648.f

View File

@ -291,10 +291,22 @@ static void test_u32_f32(void)
static void test_f32_s32(void)
{
static const float in[] = { 0.0f, 1.0f, -1.0f, 0.5f, -0.5f, 1.1f, -1.1f,
1.0f/0xa00000, 1.0f/0x1000000, -1.0f/0xa00000, -1.0f/0x1000000 };
static const int32_t out[] = { 0, 0x7fffff00, 0x80000000, 0x40000000, 0xc0000000,
0x7fffff00, 0x80000000,
0x00000100, 0x00000000, 0xffffff00, 0x00000000 };
1.0f/0xa00000, -1.0f/0xa00000, 1.0f/0x800000, -1.0f/0x800000,
1.0f/0x1000000, -1.0f/0x1000000, 1.0f/0x2000000, -1.0f/0x2000000,
1.0f/0x4000000, -1.0f/0x4000000, 1.0f/0x8000000, -1.0f/0x8000000,
1.0f/0x10000000, -1.0f/0x10000000, 1.0f/0x20000000, -1.0f/0x20000000,
1.0f/0x40000000, -1.0f/0x40000000, 1.0f/0x80000000, -1.0f/0x80000000,
1.0f/0x100000000, -1.0f/0x100000000, 1.0f/0x200000000, -1.0f/0x200000000,
};
static const int32_t out[] = { 0x00000000, 0x7fffff00, 0x80000000,
0x40000000, 0xc0000000, 0x7fffff00, 0x80000000, 0x00000100,
0xffffff00, 0x00000100, 0xffffff00, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000,
};
run_test("test_f32_s32", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out),
true, true, conv_f32_to_s32_c);
@ -320,8 +332,15 @@ static void test_f32_s32(void)
static void test_s32_f32(void)
{
static const int32_t in[] = { 0, 0x7fffff00, 0x80000000, 0x40000000, 0xc0000000 };
static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5, -0.5, };
static const int32_t in[] = { 0, 0x7FFFFFFF, 0x80000000, 0x7fffff00,
0x80000100, 0x40000000, 0xc0000000, 0x0080, 0xFFFFFF80, 0x0100,
0xFFFFFF00, 0x0200, 0xFFFFFE00
};
static const float out[] = { 0.e+00f, 9.9999988079071044921875e-01f, -1.e+00f,
9.9999988079071044921875e-01f, -9.9999988079071044921875e-01f, 5.e-01f,
-5.e-01f, 0.e+00f, -1.1920928955078125e-07f, 1.1920928955078125e-07f,
-1.1920928955078125e-07f, 2.384185791015625e-07f, -2.384185791015625e-07f
};
run_test("test_s32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out),
true, false, conv_s32_to_f32d_c);
@ -600,16 +619,102 @@ static void test_lossless_u24(void)
}
}
static void test_lossless_s32(void)
static void test_lossless_s25_32_to_f32_to_s25_32(void)
{
int32_t i;
fprintf(stderr, "test %s:\n", __func__);
for (i = S32_MIN; i < S32_MAX; i+=255) {
for (i = S25_MIN; i <= S25_MAX; i+=1) {
float v = S25_32_TO_F32(i);
int32_t t = F32_TO_S25_32(v);
spa_assert_se(i == t);
}
}
static void test_lossless_s25_32_to_s32_to_f32_to_s25_32_XFAIL(void)
{
int32_t i;
int all_lossless = 1;
int32_t max_abs_err = -1;
fprintf(stderr, "test %s:\n", __func__);
for (i = S25_MIN; i <= S25_MAX; i+=1) {
float v = S32_TO_F32(S25_32_TO_S32(i));
int32_t t = F32_TO_S25_32(v);
all_lossless &= i == t;
max_abs_err = SPA_MAX(max_abs_err, SPA_ABS(i - t));
}
spa_assert_se(!all_lossless);
spa_assert_se(max_abs_err == 1);
}
static void test_lossless_s25_32_to_s32_to_f32_to_s32_to_s25_32_XFAIL(void)
{
int32_t i;
int all_lossless = 1;
int32_t max_abs_err = -1;
fprintf(stderr, "test %s:\n", __func__);
for (i = S25_MIN; i <= S25_MAX; i+=1) {
float v = S32_TO_F32(S25_32_TO_S32(i));
int32_t t = S32_TO_S25_32(F32_TO_S32(v));
all_lossless &= i == t;
max_abs_err = SPA_MAX(max_abs_err, SPA_ABS(i - t));
}
spa_assert_se(!all_lossless);
spa_assert_se(max_abs_err == 1);
}
static void test_lossless_s25_32_to_f32_to_s32_to_s25_32_XFAIL(void)
{
int32_t i;
int all_lossless = 1;
int32_t max_abs_err = -1;
fprintf(stderr, "test %s:\n", __func__);
for (i = S25_MIN; i <= S25_MAX; i+=1) {
float v = S25_32_TO_F32(i);
int32_t t = S32_TO_S25_32(F32_TO_S32(v));
all_lossless &= i == t;
max_abs_err = SPA_MAX(max_abs_err, SPA_ABS(i - t));
}
spa_assert_se(!all_lossless);
spa_assert_se(max_abs_err == 1);
}
static void test_lossless_s32(void)
{
int64_t i;
int32_t max_abs_err = -1;
const int32_t expected_max_abs_err = 255;
fprintf(stderr, "test %s:\n", __func__);
for (i = S32_MIN; i < S32_MAX; i += (expected_max_abs_err >> 1)) {
float v = S32_TO_F32(i);
int32_t t = F32_TO_S32(v);
spa_assert_se(SPA_ABS(i - t) <= 256);
max_abs_err = SPA_MAX(max_abs_err, SPA_ABS(i - t));
}
spa_assert_se(max_abs_err == expected_max_abs_err);
}
static void test_lossless_s32_lossless_subset(void)
{
int32_t i, j;
int all_lossless = 1;
int32_t max_abs_err = -1;
fprintf(stderr, "test %s:\n", __func__);
for (i = S25_MIN; i <= S25_MAX; i+=1) {
for(j = 0; j < 8; ++j) {
int32_t s = i * (1<<j);
float v = S32_TO_F32(s);
int32_t t = F32_TO_S32(v);
all_lossless &= s == t;
max_abs_err = SPA_MAX(max_abs_err, SPA_ABS(s - t));
}
}
spa_assert_se(!all_lossless);
spa_assert_se(max_abs_err == 255);
}
static void test_lossless_u32(void)
@ -775,7 +880,12 @@ int main(int argc, char *argv[])
test_lossless_u16();
test_lossless_s24();
test_lossless_u24();
test_lossless_s25_32_to_f32_to_s25_32();
test_lossless_s25_32_to_s32_to_f32_to_s25_32_XFAIL();
test_lossless_s25_32_to_s32_to_f32_to_s32_to_s25_32_XFAIL();
test_lossless_s25_32_to_f32_to_s32_to_s25_32_XFAIL();
test_lossless_s32();
test_lossless_s32_lossless_subset();
test_lossless_u32();
test_swaps();