Last active
June 10, 2026 21:30
-
-
Save imaami/ca7aaf2e0748ce363a9650e5d5af6f7c to your computer and use it in GitHub Desktop.
Faithfully replicating a bad way to do rounding
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * gcc -O2 -march=native -mtune=native -flto=auto rounding.c -o rounding -lm | |
| */ | |
| #define _GNU_SOURCE | |
| #include <ctype.h> | |
| #include <errno.h> | |
| #include <fenv.h> | |
| #include <math.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| /** | |
| * @brief Round `d` to approximately `n` decimal places (as close to | |
| * the correct result as possible with IEEE754 floating-point) | |
| * | |
| * The result is equal to the result of parsing `snprintf()`'s output | |
| * with `atof()`, with equivalent rounding done in the format string. | |
| */ | |
| #define round_to_precision(n, d) _Generic( \ | |
| &(char[((n) < 1) + ((n) > 0) * (n)]){0} , char(*)[ 1]:round1 \ | |
| , char(*)[ 2]:round2 , char(*)[ 3]:round3 , char(*)[ 4]:round4 \ | |
| , char(*)[ 5]:round5 , char(*)[ 6]:round6 , char(*)[ 7]:round7 \ | |
| , char(*)[ 8]:round8 , char(*)[ 9]:round9 , char(*)[10]:round10 \ | |
| , char(*)[11]:round11 , char(*)[12]:round12 , char(*)[13]:round13 \ | |
| , char(*)[14]:round14 , char(*)[15]:round15 , char(*)[16]:round16 \ | |
| , char(*)[17]:round17 , char(*)[18]:round18 , char(*)[19]:round19 \ | |
| , char(*)[20]:round20 , char(*)[21]:round21 , char(*)[22]:round22)(d) | |
| #define precision_mul(n) _Generic( \ | |
| &(char[((n) < 1) + ((n) > 0) * (n)]){0} \ | |
| , char(*)[ 1]: 10.0L \ | |
| , char(*)[ 2]: 100.0L \ | |
| , char(*)[ 3]: 1000.0L \ | |
| , char(*)[ 4]: 10000.0L \ | |
| , char(*)[ 5]: 100000.0L \ | |
| , char(*)[ 6]: 1000000.0L \ | |
| , char(*)[ 7]: 10000000.0L \ | |
| , char(*)[ 8]: 100000000.0L \ | |
| , char(*)[ 9]: 1000000000.0L \ | |
| , char(*)[10]: 10000000000.0L \ | |
| , char(*)[11]: 100000000000.0L \ | |
| , char(*)[12]: 1000000000000.0L \ | |
| , char(*)[13]: 10000000000000.0L \ | |
| , char(*)[14]: 100000000000000.0L \ | |
| , char(*)[15]: 1000000000000000.0L \ | |
| , char(*)[16]: 10000000000000000.0L \ | |
| , char(*)[17]: 100000000000000000.0L \ | |
| , char(*)[18]: 1000000000000000000.0L \ | |
| , char(*)[19]: 10000000000000000000.0L \ | |
| , char(*)[20]: 100000000000000000000.0L \ | |
| , char(*)[21]: 1000000000000000000000.0L \ | |
| , char(*)[22]:10000000000000000000000.0L) | |
| #define define_round_function(p) \ | |
| __attribute__((always_inline)) \ | |
| static inline double \ | |
| round##p (double d) { \ | |
| const long double d_ = (long double)d; \ | |
| const long double t = truncl(d_); \ | |
| return (double)(nearbyintl(precision_mul(p) \ | |
| * (d_ - t)) / precision_mul(p) + t); \ | |
| } _Static_assert(1, "") | |
| define_round_function(22); | |
| define_round_function(21); | |
| define_round_function(20); | |
| define_round_function(19); | |
| define_round_function(18); | |
| define_round_function(17); | |
| define_round_function(16); | |
| define_round_function(15); | |
| define_round_function(14); | |
| define_round_function(13); | |
| define_round_function(12); | |
| define_round_function(11); | |
| define_round_function(10); | |
| define_round_function( 9); | |
| define_round_function( 8); | |
| define_round_function( 7); | |
| define_round_function( 6); | |
| define_round_function( 5); | |
| define_round_function( 4); | |
| define_round_function( 3); | |
| define_round_function( 2); | |
| define_round_function( 1); | |
| #undef define_round_function | |
| #undef precision_mul | |
| #define TEST(n, d, b, t, f) do { \ | |
| if (0 < snprintf(b, sizeof b, "%." #n "lf", d)) \ | |
| { \ | |
| double p = atof(b); \ | |
| double q = round_to_precision(n, d); \ | |
| printf("%27.22lf (printf + atof)\n" \ | |
| "%27.22lf (round" #n ")\n", p, q); \ | |
| int failure = p != q; \ | |
| if (failure) \ | |
| fputs("FAIL: round" #n "\n", stderr); \ | |
| f += failure; \ | |
| t += 1; \ | |
| } \ | |
| } while (0) | |
| int | |
| main (int c, | |
| char **v) | |
| { | |
| (void)fesetround(FE_TONEAREST); | |
| int failed = 0; | |
| int tested = 0; | |
| for (int i = 0; ++i < c;) { | |
| errno = 0; | |
| char *e = v[i]; | |
| long long n = strtoll(v[i], &e, 0); | |
| if (errno || e == v[i] || (*e && !isspace(*e))) | |
| continue; | |
| const double d = n ? (double)(1000.0L / (long double)n) : 1000.0; | |
| char buf[256]; | |
| TEST(22, d, buf, tested, failed); | |
| TEST(21, d, buf, tested, failed); | |
| TEST(20, d, buf, tested, failed); | |
| TEST(19, d, buf, tested, failed); | |
| TEST(18, d, buf, tested, failed); | |
| TEST(17, d, buf, tested, failed); | |
| TEST(16, d, buf, tested, failed); | |
| TEST(15, d, buf, tested, failed); | |
| TEST(14, d, buf, tested, failed); | |
| TEST(13, d, buf, tested, failed); | |
| TEST(12, d, buf, tested, failed); | |
| TEST(11, d, buf, tested, failed); | |
| TEST(10, d, buf, tested, failed); | |
| TEST( 9, d, buf, tested, failed); | |
| TEST( 8, d, buf, tested, failed); | |
| TEST( 7, d, buf, tested, failed); | |
| TEST( 6, d, buf, tested, failed); | |
| TEST( 5, d, buf, tested, failed); | |
| TEST( 4, d, buf, tested, failed); | |
| TEST( 3, d, buf, tested, failed); | |
| TEST( 2, d, buf, tested, failed); | |
| TEST( 1, d, buf, tested, failed); | |
| } | |
| if (tested) | |
| printf("passed %d of %d tests\n", | |
| tested - failed, tested); | |
| return tested && failed; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment