5
5
#include < algorithm>
6
6
#include < deque>
7
7
#include < inttypes.h>
8
+ #include < numeric>
8
9
#include < optional>
9
10
#include < queue>
11
+ #include < stdint.h>
10
12
#include < type_traits>
11
13
#include < unordered_set>
14
+ #include < utility>
12
15
13
16
#include " helpers.hpp"
14
17
@@ -199,21 +202,44 @@ class AssignedProtos {
199
202
return colors.size () <= options.maxOpaqueColors ();
200
203
}
201
204
205
+ // The `relSizeOf` method below should compute the sum, for each color in `protoPal`, of
206
+ // the reciprocal of the "multiplicity" of the color across "our" proto-palettes.
207
+ // However, literally computing the reciprocals would involve floating-point division, which
208
+ // leads to imprecision and even platform-specific differences.
209
+ // We avoid this by multiplying the reciprocals by a factor such that division always produces
210
+ // an integer; the LCM of all values the denominator can take is the smallest suitable factor.
211
+ static constexpr uint32_t scaleFactor = [] {
212
+ // Fold over 1..=17 with the associative LCM function
213
+ // (17 is the largest the denominator in `relSizeOf` below can be)
214
+ uint32_t factor = 1 ;
215
+ for (uint32_t n = 2 ; n <= 17 ; ++n) {
216
+ factor = std::lcm (factor, n);
217
+ }
218
+ return factor;
219
+ }();
220
+
202
221
/*
203
- * Computes the "relative size" of a proto-palette on this palette
222
+ * Computes the "relative size" of a proto-palette on this palette;
223
+ * it's a measure of how much this proto-palette would "cost" to introduce.
204
224
*/
205
- double relSizeOf (ProtoPalette const &protoPal) const {
225
+ uint32_t relSizeOf (ProtoPalette const &protoPal) const {
206
226
// NOTE: this function must not call `uniqueColors`, or one of its callers will break!
207
- double relSize = 0 .;
227
+
228
+ uint32_t relSize = 0 ;
208
229
for (uint16_t color : protoPal) {
209
- auto n = std::count_if (RANGE (*this ), [this , &color](ProtoPalAttrs const &attrs) {
210
- ProtoPalette const &pal = (*_protoPals)[attrs.protoPalIndex ];
211
- return std::find (RANGE (pal), color) != pal.end ();
212
- });
213
- // NOTE: The paper and the associated code disagree on this: the code has
214
- // this `1 +`, whereas the paper does not; its lack causes a division by 0
215
- // if the symbol is not found anywhere, so I'm assuming the paper is wrong.
216
- relSize += 1 . / (1 + n);
230
+ auto multiplicity = // How many of our proto-palettes does this color also belong to?
231
+ std::count_if (RANGE (*this ), [this , &color](ProtoPalAttrs const &attrs) {
232
+ ProtoPalette const &pal = (*_protoPals)[attrs.protoPalIndex ];
233
+ return std::find (RANGE (pal), color) != pal.end ();
234
+ });
235
+ // We increase the denominator by 1 here; the reference code does this,
236
+ // but the paper does not. Not adding 1 makes a multiplicity of 0 cause a division by 0
237
+ // (that is, if the color is not found in any proto-palette), and adding 1 still seems
238
+ // to preserve the paper's reasoning.
239
+ //
240
+ // The scale factor should ensure integer divisions only.
241
+ assume (scaleFactor % (multiplicity + 1 ) == 0 );
242
+ relSize += scaleFactor / (multiplicity + 1 );
217
243
}
218
244
return relSize;
219
245
}
@@ -383,18 +409,18 @@ std::tuple<DefaultInitVec<size_t>, size_t>
383
409
size_t bestPalIndex = assignments.size ();
384
410
// We're looking for a palette where the proto-palette's relative size is less than
385
411
// its actual size; so only overwrite the "not found" index on meeting that criterion
386
- double bestRelSize = protoPal.size ();
412
+ uint32_t bestRelSize = protoPal.size () * AssignedProtos::scaleFactor ;
387
413
388
414
for (size_t i = 0 ; i < assignments.size (); ++i) {
389
415
// Skip the page if this one is banned from it
390
416
if (attrs.isBannedFrom (i)) {
391
417
continue ;
392
418
}
393
419
394
- double relSize = assignments[i].relSizeOf (protoPal);
420
+ uint32_t relSize = assignments[i].relSizeOf (protoPal);
395
421
options.verbosePrint (
396
422
Options::VERB_TRACE,
397
- " Relative size to palette %zu (of %zu): %.20f (size = %zu)\n " ,
423
+ " Relative size to palette %zu (of %zu): %" PRIu32 " (size = %zu)\n " ,
398
424
i,
399
425
assignments.size (),
400
426
relSize,
@@ -444,23 +470,23 @@ std::tuple<DefaultInitVec<size_t>, size_t>
444
470
ProtoPalette const &rhsProtoPal = protoPalettes[rhs.protoPalIndex ];
445
471
size_t lhsSize = lhsProtoPal.size ();
446
472
size_t rhsSize = rhsProtoPal.size ();
447
- double lhsRelSize = bestPal.relSizeOf (lhsProtoPal);
448
- double rhsRelSize = bestPal.relSizeOf (rhsProtoPal);
473
+ uint32_t lhsRelSize = bestPal.relSizeOf (lhsProtoPal);
474
+ uint32_t rhsRelSize = bestPal.relSizeOf (rhsProtoPal);
449
475
450
- // This comparison is algebraically equivalent to
451
- // `lhsSize / lhsRelSize < rhsSize / rhsRelSize`,
452
- // but without potential precision loss from floating-point division.
453
476
options.verbosePrint (
454
477
Options::VERB_TRACE,
455
- " Proto-palettes %zu <=> %zu: Efficiency: %zu / %.20f <=> %zu / "
456
- " %.20f \n " ,
478
+ " Proto-palettes %zu <=> %zu: Efficiency: %zu / %" PRIu32 " <=> %zu / "
479
+ " %" PRIu32 " \n " ,
457
480
lhs.protoPalIndex ,
458
481
rhs.protoPalIndex ,
459
482
lhsSize,
460
483
lhsRelSize,
461
484
rhsSize,
462
485
rhsRelSize
463
486
);
487
+ // This comparison is algebraically equivalent to
488
+ // `lhsSize / lhsRelSize < rhsSize / rhsRelSize`,
489
+ // but without potential precision loss from floating-point division.
464
490
return lhsSize * rhsRelSize < rhsSize * lhsRelSize;
465
491
}
466
492
);
@@ -471,23 +497,23 @@ std::tuple<DefaultInitVec<size_t>, size_t>
471
497
ProtoPalette const &maxProtoPal = protoPalettes[maxEfficiencyIter->protoPalIndex ];
472
498
size_t minSize = minProtoPal.size ();
473
499
size_t maxSize = maxProtoPal.size ();
474
- double minRelSize = bestPal.relSizeOf (minProtoPal);
475
- double maxRelSize = bestPal.relSizeOf (maxProtoPal);
476
- // This comparison is algebraically equivalent to
477
- // `maxSize / maxRelSize - minSize / minRelSize < .001`,
478
- // but without potential precision loss from floating-point division.
479
- // TODO: yikes for float comparison! I *think* this threshold is OK?
500
+ uint32_t minRelSize = bestPal.relSizeOf (minProtoPal);
501
+ uint32_t maxRelSize = bestPal.relSizeOf (maxProtoPal);
480
502
options.verbosePrint (
481
503
Options::VERB_TRACE,
482
- " Proto-palettes %zu <= %zu: Efficiency: %zu / %.20f <= %zu / %.20f\n " ,
504
+ " Proto-palettes %zu <= %zu: Efficiency: %zu / %" PRIu32 " <= %zu / %" PRIu32
505
+ " \n " ,
483
506
minEfficiencyIter->protoPalIndex ,
484
507
maxEfficiencyIter->protoPalIndex ,
485
508
minSize,
486
509
minRelSize,
487
510
maxSize,
488
511
maxRelSize
489
512
);
490
- if (maxSize * minRelSize - minSize * maxRelSize < minRelSize * maxRelSize * .001 ) {
513
+ // This comparison is algebraically equivalent to
514
+ // `maxSize / maxRelSize == minSize / minRelSize`,
515
+ // but without potential precision loss from floating-point division.
516
+ if (maxSize * minRelSize == minSize * maxRelSize) {
491
517
options.verbosePrint (Options::VERB_TRACE, " All efficiencies are identical\n " );
492
518
break ;
493
519
}
0 commit comments