TemporalRoundingMode.h (26634B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef builtin_temporal_TemporalRoundingMode_h 8 #define builtin_temporal_TemporalRoundingMode_h 9 10 #include "mozilla/Assertions.h" 11 12 #include <cmath> 13 #include <stdint.h> 14 15 #include "vm/Int128.h" 16 17 namespace js::temporal { 18 19 // Overview of integer rounding modes is available at 20 // <https://en.wikipedia.org/wiki/Rounding#Rounding_to_integer>. 21 enum class TemporalRoundingMode { 22 // 1. Directed rounding to an integer. 23 24 // Round toward positive infinity. 25 Ceil, 26 27 // Round toward negative infinity. 28 Floor, 29 30 // Round toward infinity or round away from zero. 31 Expand, 32 33 // Round toward zero or round away from infinity. 34 Trunc, 35 36 // 2. Rounding to the nearest integer. 37 38 // Round half toward positive infinity. 39 HalfCeil, 40 41 // Round half toward negative infinity. 42 HalfFloor, 43 44 // Round half toward infinity or round half away from zero. 45 HalfExpand, 46 47 // Round half toward zero or round half away from infinity. 48 HalfTrunc, 49 50 // Round half to even. 51 HalfEven, 52 }; 53 54 /** 55 * NegateRoundingMode ( roundingMode ) 56 */ 57 constexpr auto NegateRoundingMode(TemporalRoundingMode roundingMode) { 58 // Steps 1-5. 59 switch (roundingMode) { 60 case TemporalRoundingMode::Ceil: 61 return TemporalRoundingMode::Floor; 62 63 case TemporalRoundingMode::Floor: 64 return TemporalRoundingMode::Ceil; 65 66 case TemporalRoundingMode::HalfCeil: 67 return TemporalRoundingMode::HalfFloor; 68 69 case TemporalRoundingMode::HalfFloor: 70 return TemporalRoundingMode::HalfCeil; 71 72 case TemporalRoundingMode::Expand: 73 case TemporalRoundingMode::Trunc: 74 case TemporalRoundingMode::HalfExpand: 75 case TemporalRoundingMode::HalfTrunc: 76 case TemporalRoundingMode::HalfEven: 77 return roundingMode; 78 } 79 MOZ_CRASH("invalid rounding mode"); 80 } 81 82 /** 83 * Adjust the rounding mode to round negative values in the same direction as 84 * positive values. 85 */ 86 constexpr auto ToPositiveRoundingMode(TemporalRoundingMode roundingMode) { 87 switch (roundingMode) { 88 case TemporalRoundingMode::Ceil: 89 case TemporalRoundingMode::Floor: 90 case TemporalRoundingMode::HalfCeil: 91 case TemporalRoundingMode::HalfFloor: 92 case TemporalRoundingMode::HalfEven: 93 // (Half-)Ceil/Floor round toward the same infinity for negative and 94 // positive values, so the rounding mode doesn't need to be adjusted. The 95 // same applies for half-even rounding. 96 return roundingMode; 97 98 case TemporalRoundingMode::Expand: 99 // Expand rounds positive values toward +infinity, but negative values 100 // toward -infinity. Adjust the rounding mode to Ceil to round negative 101 // values in the same direction as positive values. 102 return TemporalRoundingMode::Ceil; 103 104 case TemporalRoundingMode::Trunc: 105 // Truncation rounds positive values down toward zero, but negative values 106 // up toward zero. Adjust the rounding mode to Floor to round negative 107 // values in the same direction as positive values. 108 return TemporalRoundingMode::Floor; 109 110 case TemporalRoundingMode::HalfExpand: 111 // Adjust the rounding mode to Half-Ceil, similar to the Expand case. 112 return TemporalRoundingMode::HalfCeil; 113 114 case TemporalRoundingMode::HalfTrunc: 115 // Adjust the rounding mode to Half-Floor, similar to the Trunc case. 116 return TemporalRoundingMode::HalfFloor; 117 } 118 MOZ_CRASH("unexpected rounding mode"); 119 } 120 121 // Temporal performs division on "mathematical values" [1] with implies using 122 // infinite precision. This rules out using IEE-754 floating point types like 123 // `double`. It also means we can't implement the algorithms from the 124 // specification verbatim, but instead have to translate them into equivalent 125 // operations. 126 // 127 // Throughout the following division functions, the divisor is required to be 128 // positive. This allows to simplify the implementation, because it ensures 129 // non-zero quotient and remainder values have the same sign as the dividend. 130 // 131 // [1] https://tc39.es/ecma262/#mathematical-value 132 133 /** 134 * Compute ceiling division ⌈dividend / divisor⌉. The divisor must be a positive 135 * number. 136 */ 137 constexpr int64_t CeilDiv(int64_t dividend, int64_t divisor) { 138 MOZ_ASSERT(divisor > 0, "negative divisor not supported"); 139 140 // NB: Division and modulo operation are fused into a single machine code 141 // instruction by the compiler. 142 int64_t quotient = dividend / divisor; 143 int64_t remainder = dividend % divisor; 144 145 // Ceiling division rounds the quotient toward positive infinity. When the 146 // quotient is negative, this is equivalent to rounding toward zero. See [1]. 147 // 148 // int64_t division truncates, so rounding toward zero for negative quotients 149 // is already covered. When there is a non-zero positive remainder, the 150 // quotient is positive and we have to increment it by one to implement 151 // rounding toward positive infinity. 152 // 153 // [1] 154 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 155 if (remainder > 0) { 156 quotient += 1; 157 } 158 return quotient; 159 } 160 161 /** 162 * Compute floor division ⌊dividend / divisor⌋. The divisor must be a positive 163 * number. 164 */ 165 constexpr int64_t FloorDiv(int64_t dividend, int64_t divisor) { 166 MOZ_ASSERT(divisor > 0, "negative divisor not supported"); 167 168 // NB: Division and modulo operation are fused into a single machine code 169 // instruction by the compiler. 170 int64_t quotient = dividend / divisor; 171 int64_t remainder = dividend % divisor; 172 173 // Floor division rounds the quotient toward negative infinity. When the 174 // quotient is positive, this is equivalent to rounding toward zero. See [1]. 175 // 176 // int64_t division truncates, so rounding toward zero for positive quotients 177 // is already covered. When there is a non-zero negative remainder, the 178 // quotient is negative and we have to decrement it by one to implement 179 // rounding toward negative infinity. 180 // 181 // [1] 182 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 183 if (remainder < 0) { 184 quotient -= 1; 185 } 186 return quotient; 187 } 188 189 /** 190 * Compute "round toward infinity" division `dividend / divisor`. The divisor 191 * must be a positive number. 192 */ 193 constexpr int64_t ExpandDiv(int64_t dividend, int64_t divisor) { 194 MOZ_ASSERT(divisor > 0, "negative divisor not supported"); 195 196 // NB: Division and modulo operation are fused into a single machine code 197 // instruction by the compiler. 198 int64_t quotient = dividend / divisor; 199 int64_t remainder = dividend % divisor; 200 201 // "Round toward infinity" division rounds positive quotients toward positive 202 // infinity and negative quotients toward negative infinity. See [1]. 203 // 204 // When there is a non-zero positive remainder, the quotient is positive and 205 // we have to increment it by one to implement rounding toward positive 206 // infinity. When there is a non-zero negative remainder, the quotient is 207 // negative and we have to decrement it by one to implement rounding toward 208 // negative infinity. 209 // 210 // [1] 211 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 212 if (remainder > 0) { 213 quotient += 1; 214 } 215 if (remainder < 0) { 216 quotient -= 1; 217 } 218 return quotient; 219 } 220 221 /** 222 * Compute truncating division `dividend / divisor`. The divisor must be a 223 * positive number. 224 */ 225 constexpr int64_t TruncDiv(int64_t dividend, int64_t divisor) { 226 MOZ_ASSERT(divisor > 0, "negative divisor not supported"); 227 228 // Truncating division rounds both positive and negative quotients toward 229 // zero, cf. [1]. 230 // 231 // int64_t division truncates, so rounding toward zero implicitly happens. 232 // 233 // [1] 234 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 235 return dividend / divisor; 236 } 237 238 /** 239 * Compute "round half toward positive infinity" division `dividend / divisor`. 240 * The divisor must be a positive number. 241 */ 242 inline int64_t HalfCeilDiv(int64_t dividend, int64_t divisor) { 243 MOZ_ASSERT(divisor > 0, "negative divisor not supported"); 244 245 // NB: Division and modulo operation are fused into a single machine code 246 // instruction by the compiler. 247 int64_t quotient = dividend / divisor; 248 int64_t remainder = dividend % divisor; 249 250 // "Round half toward positive infinity" division rounds the quotient toward 251 // positive infinity when the fractional part of the remainder is ≥0.5. When 252 // the quotient is negative, this is equivalent to rounding toward zero 253 // instead of toward positive infinity. See [1]. 254 // 255 // When the remainder is a non-zero positive value, the quotient is positive, 256 // too. When additionally the fractional part of the remainder is ≥0.5, we 257 // have to increment the quotient by one to implement rounding toward positive 258 // infinity. 259 // 260 // int64_t division truncates, so we implicitly round toward zero for negative 261 // quotients. When the absolute value of the fractional part of the remainder 262 // is >0.5, we should instead have rounded toward negative infinity, so we 263 // need to decrement the quotient by one. 264 // 265 // [1] 266 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 267 if (remainder > 0 && uint64_t(std::abs(remainder)) * 2 >= uint64_t(divisor)) { 268 quotient += 1; 269 } 270 if (remainder < 0 && uint64_t(std::abs(remainder)) * 2 > uint64_t(divisor)) { 271 quotient -= 1; 272 } 273 return quotient; 274 } 275 276 /** 277 * Compute "round half toward negative infinity" division `dividend / divisor`. 278 * The divisor must be a positive number. 279 */ 280 inline int64_t HalfFloorDiv(int64_t dividend, int64_t divisor) { 281 MOZ_ASSERT(divisor > 0, "negative divisor not supported"); 282 283 // NB: Division and modulo operation are fused into a single machine code 284 // instruction by the compiler. 285 int64_t quotient = dividend / divisor; 286 int64_t remainder = dividend % divisor; 287 288 // "Round half toward negative infinity" division rounds the quotient toward 289 // negative infinity when the fractional part of the remainder is ≥0.5. When 290 // the quotient is positive, this is equivalent to rounding toward zero 291 // instead of toward negative infinity. See [1]. 292 // 293 // When the remainder is a non-zero negative value, the quotient is negative, 294 // too. When additionally the fractional part of the remainder is ≥0.5, we 295 // have to decrement the quotient by one to implement rounding toward negative 296 // infinity. 297 // 298 // int64_t division truncates, so we implicitly round toward zero for positive 299 // quotients. When the absolute value of the fractional part of the remainder 300 // is >0.5, we should instead have rounded toward positive infinity, so we 301 // need to increment the quotient by one. 302 // 303 // [1] 304 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 305 if (remainder < 0 && uint64_t(std::abs(remainder)) * 2 >= uint64_t(divisor)) { 306 quotient -= 1; 307 } 308 if (remainder > 0 && uint64_t(std::abs(remainder)) * 2 > uint64_t(divisor)) { 309 quotient += 1; 310 } 311 return quotient; 312 } 313 314 /** 315 * Compute "round half toward infinity" division `dividend / divisor`. The 316 * divisor must be a positive number. 317 */ 318 inline int64_t HalfExpandDiv(int64_t dividend, int64_t divisor) { 319 MOZ_ASSERT(divisor > 0, "negative divisor not supported"); 320 321 // NB: Division and modulo operation are fused into a single machine code 322 // instruction by the compiler. 323 int64_t quotient = dividend / divisor; 324 int64_t remainder = dividend % divisor; 325 326 // "Round half toward infinity" division rounds positive quotients whose 327 // remainder has a fractional part ≥0.5 toward positive infinity. And negative 328 // quotients whose remainder has a fractional part ≥0.5 toward negative 329 // infinity. See [1]. 330 // 331 // int64_t division truncates, which means it rounds toward zero, so we have 332 // to increment resp. decrement the quotient when the fractional part of the 333 // remainder is ≥0.5 to round toward ±infinity. 334 // 335 // [1] 336 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 337 if (uint64_t(std::abs(remainder)) * 2 >= uint64_t(divisor)) { 338 quotient += (dividend > 0) ? 1 : -1; 339 } 340 return quotient; 341 } 342 343 /** 344 * Compute "round half toward zero" division `dividend / divisor`. The divisor 345 * must be a positive number. 346 */ 347 inline int64_t HalfTruncDiv(int64_t dividend, int64_t divisor) { 348 MOZ_ASSERT(divisor > 0, "negative divisor not supported"); 349 350 // NB: Division and modulo operation are fused into a single machine code 351 // instruction by the compiler. 352 int64_t quotient = dividend / divisor; 353 int64_t remainder = dividend % divisor; 354 355 // "Round half toward zero" division rounds both positive and negative 356 // quotients whose remainder has a fractional part ≤0.5 toward zero. See [1]. 357 // 358 // int64_t division truncates, so we implicitly round toward zero. When the 359 // fractional part of the remainder is >0.5, we should instead have rounded 360 // toward ±infinity, so we need to increment resp. decrement the quotient by 361 // one. 362 // 363 // [1] 364 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 365 if (uint64_t(std::abs(remainder)) * 2 > uint64_t(divisor)) { 366 quotient += (dividend > 0) ? 1 : -1; 367 } 368 return quotient; 369 } 370 371 /** 372 * Compute "round half to even" division `dividend / divisor`. The divisor must 373 * be a positive number. 374 */ 375 inline int64_t HalfEvenDiv(int64_t dividend, int64_t divisor) { 376 MOZ_ASSERT(divisor > 0, "negative divisor not supported"); 377 378 // NB: Division and modulo operation are fused into a single machine code 379 // instruction by the compiler. 380 int64_t quotient = dividend / divisor; 381 int64_t remainder = dividend % divisor; 382 383 // "Round half to even" division rounds both positive and negative quotients 384 // to the nearest even integer. See [1]. 385 // 386 // int64_t division truncates, so we implicitly round toward zero. When the 387 // fractional part of the remainder is 0.5 and the quotient is odd or when the 388 // fractional part of the remainder is >0.5, we should instead have rounded 389 // toward ±infinity, so we need to increment resp. decrement the quotient by 390 // one. 391 // 392 // [1] 393 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 394 if ((quotient & 1) == 1 && 395 uint64_t(std::abs(remainder)) * 2 == uint64_t(divisor)) { 396 quotient += (dividend > 0) ? 1 : -1; 397 } 398 if (uint64_t(std::abs(remainder)) * 2 > uint64_t(divisor)) { 399 quotient += (dividend > 0) ? 1 : -1; 400 } 401 return quotient; 402 } 403 404 /** 405 * Perform `dividend / divisor` and round the result according to the given 406 * rounding mode. 407 */ 408 inline int64_t Divide(int64_t dividend, int64_t divisor, 409 TemporalRoundingMode roundingMode) { 410 switch (roundingMode) { 411 case TemporalRoundingMode::Ceil: 412 return CeilDiv(dividend, divisor); 413 case TemporalRoundingMode::Floor: 414 return FloorDiv(dividend, divisor); 415 case TemporalRoundingMode::Expand: 416 return ExpandDiv(dividend, divisor); 417 case TemporalRoundingMode::Trunc: 418 return TruncDiv(dividend, divisor); 419 case TemporalRoundingMode::HalfCeil: 420 return HalfCeilDiv(dividend, divisor); 421 case TemporalRoundingMode::HalfFloor: 422 return HalfFloorDiv(dividend, divisor); 423 case TemporalRoundingMode::HalfExpand: 424 return HalfExpandDiv(dividend, divisor); 425 case TemporalRoundingMode::HalfTrunc: 426 return HalfTruncDiv(dividend, divisor); 427 case TemporalRoundingMode::HalfEven: 428 return HalfEvenDiv(dividend, divisor); 429 } 430 MOZ_CRASH("invalid rounding mode"); 431 } 432 433 /** 434 * Compute ceiling division ⌈dividend / divisor⌉. The divisor must be a positive 435 * number. 436 */ 437 inline Int128 CeilDiv(const Int128& dividend, const Int128& divisor) { 438 MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported"); 439 440 auto [quotient, remainder] = dividend.divrem(divisor); 441 442 // Ceiling division rounds the quotient toward positive infinity. When the 443 // quotient is negative, this is equivalent to rounding toward zero. See [1]. 444 // 445 // Int128 division truncates, so rounding toward zero for negative quotients 446 // is already covered. When there is a non-zero positive remainder, the 447 // quotient is positive and we have to increment it by one to implement 448 // rounding toward positive infinity. 449 // 450 // [1] 451 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 452 if (remainder > Int128{0}) { 453 quotient += Int128{1}; 454 } 455 return quotient; 456 } 457 458 /** 459 * Compute floor division ⌊dividend / divisor⌋. The divisor must be a positive 460 * number. 461 */ 462 inline Int128 FloorDiv(const Int128& dividend, const Int128& divisor) { 463 MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported"); 464 465 auto [quotient, remainder] = dividend.divrem(divisor); 466 467 // Floor division rounds the quotient toward negative infinity. When the 468 // quotient is positive, this is equivalent to rounding toward zero. See [1]. 469 // 470 // Int128 division truncates, so rounding toward zero for positive quotients 471 // is already covered. When there is a non-zero negative remainder, the 472 // quotient is negative and we have to decrement it by one to implement 473 // rounding toward negative infinity. 474 // 475 // [1] 476 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 477 if (remainder < Int128{0}) { 478 quotient -= Int128{1}; 479 } 480 return quotient; 481 } 482 483 /** 484 * Compute "round toward infinity" division `dividend / divisor`. The divisor 485 * must be a positive number. 486 */ 487 inline Int128 ExpandDiv(const Int128& dividend, const Int128& divisor) { 488 MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported"); 489 490 auto [quotient, remainder] = dividend.divrem(divisor); 491 492 // "Round toward infinity" division rounds positive quotients toward positive 493 // infinity and negative quotients toward negative infinity. See [1]. 494 // 495 // When there is a non-zero positive remainder, the quotient is positive and 496 // we have to increment it by one to implement rounding toward positive 497 // infinity. When there is a non-zero negative remainder, the quotient is 498 // negative and we have to decrement it by one to implement rounding toward 499 // negative infinity. 500 // 501 // [1] 502 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 503 if (remainder > Int128{0}) { 504 quotient += Int128{1}; 505 } 506 if (remainder < Int128{0}) { 507 quotient -= Int128{1}; 508 } 509 return quotient; 510 } 511 512 /** 513 * Compute truncating division `dividend / divisor`. The divisor must be a 514 * positive number. 515 */ 516 inline Int128 TruncDiv(const Int128& dividend, const Int128& divisor) { 517 MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported"); 518 519 // Truncating division rounds both positive and negative quotients toward 520 // zero, cf. [1]. 521 // 522 // Int128 division truncates, so rounding toward zero implicitly happens. 523 // 524 // [1] 525 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 526 return dividend / divisor; 527 } 528 529 /** 530 * Compute "round half toward positive infinity" division `dividend / divisor`. 531 * The divisor must be a positive number. 532 */ 533 inline Int128 HalfCeilDiv(const Int128& dividend, const Int128& divisor) { 534 MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported"); 535 536 auto [quotient, remainder] = dividend.divrem(divisor); 537 538 // "Round half toward positive infinity" division rounds the quotient toward 539 // positive infinity when the fractional part of the remainder is ≥0.5. When 540 // the quotient is negative, this is equivalent to rounding toward zero 541 // instead of toward positive infinity. See [1]. 542 // 543 // When the remainder is a non-zero positive value, the quotient is positive, 544 // too. When additionally the fractional part of the remainder is ≥0.5, we 545 // have to increment the quotient by one to implement rounding toward positive 546 // infinity. 547 // 548 // Int128 division truncates, so we implicitly round toward zero for negative 549 // quotients. When the absolute value of the fractional part of the remainder 550 // is >0.5, we should instead have rounded toward negative infinity, so we 551 // need to decrement the quotient by one. 552 // 553 // [1] 554 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 555 if (remainder > Int128{0} && 556 Uint128(remainder.abs()) * Uint128{2} >= static_cast<Uint128>(divisor)) { 557 quotient += Int128{1}; 558 } 559 if (remainder < Int128{0} && 560 Uint128(remainder.abs()) * Uint128{2} > static_cast<Uint128>(divisor)) { 561 quotient -= Int128{1}; 562 } 563 return quotient; 564 } 565 566 /** 567 * Compute "round half toward negative infinity" division `dividend / divisor`. 568 * The divisor must be a positive number. 569 */ 570 inline Int128 HalfFloorDiv(const Int128& dividend, const Int128& divisor) { 571 MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported"); 572 573 auto [quotient, remainder] = dividend.divrem(divisor); 574 575 // "Round half toward negative infinity" division rounds the quotient toward 576 // negative infinity when the fractional part of the remainder is ≥0.5. When 577 // the quotient is positive, this is equivalent to rounding toward zero 578 // instead of toward negative infinity. See [1]. 579 // 580 // When the remainder is a non-zero negative value, the quotient is negative, 581 // too. When additionally the fractional part of the remainder is ≥0.5, we 582 // have to decrement the quotient by one to implement rounding toward negative 583 // infinity. 584 // 585 // Int128 division truncates, so we implicitly round toward zero for positive 586 // quotients. When the absolute value of the fractional part of the remainder 587 // is >0.5, we should instead have rounded toward positive infinity, so we 588 // need to increment the quotient by one. 589 // 590 // [1] 591 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 592 if (remainder < Int128{0} && 593 Uint128(remainder.abs()) * Uint128{2} >= static_cast<Uint128>(divisor)) { 594 quotient -= Int128{1}; 595 } 596 if (remainder > Int128{0} && 597 Uint128(remainder.abs()) * Uint128{2} > static_cast<Uint128>(divisor)) { 598 quotient += Int128{1}; 599 } 600 return quotient; 601 } 602 603 /** 604 * Compute "round half toward infinity" division `dividend / divisor`. The 605 * divisor must be a positive number. 606 */ 607 inline Int128 HalfExpandDiv(const Int128& dividend, const Int128& divisor) { 608 MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported"); 609 610 auto [quotient, remainder] = dividend.divrem(divisor); 611 612 // "Round half toward infinity" division rounds positive quotients whose 613 // remainder has a fractional part ≥0.5 toward positive infinity. And negative 614 // quotients whose remainder has a fractional part ≥0.5 toward negative 615 // infinity. See [1]. 616 // 617 // Int128 division truncates, which means it rounds toward zero, so we have 618 // to increment resp. decrement the quotient when the fractional part of the 619 // remainder is ≥0.5 to round toward ±infinity. 620 // 621 // [1] 622 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 623 if (Uint128(remainder.abs()) * Uint128{2} >= static_cast<Uint128>(divisor)) { 624 quotient += (dividend > Int128{0}) ? Int128{1} : Int128{-1}; 625 } 626 return quotient; 627 } 628 629 /** 630 * Compute "round half toward zero" division `dividend / divisor`. The divisor 631 * must be a positive number. 632 */ 633 inline Int128 HalfTruncDiv(const Int128& dividend, const Int128& divisor) { 634 MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported"); 635 636 auto [quotient, remainder] = dividend.divrem(divisor); 637 638 // "Round half toward zero" division rounds both positive and negative 639 // quotients whose remainder has a fractional part ≤0.5 toward zero. See [1]. 640 // 641 // Int128 division truncates, so we implicitly round toward zero. When the 642 // fractional part of the remainder is >0.5, we should instead have rounded 643 // toward ±infinity, so we need to increment resp. decrement the quotient by 644 // one. 645 // 646 // [1] 647 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 648 if (Uint128(remainder.abs()) * Uint128{2} > static_cast<Uint128>(divisor)) { 649 quotient += (dividend > Int128{0}) ? Int128{1} : Int128{-1}; 650 } 651 return quotient; 652 } 653 654 /** 655 * Compute "round half to even" division `dividend / divisor`. The divisor must 656 * be a positive number. 657 */ 658 inline Int128 HalfEvenDiv(const Int128& dividend, const Int128& divisor) { 659 MOZ_ASSERT(divisor > Int128{0}, "negative divisor not supported"); 660 661 auto [quotient, remainder] = dividend.divrem(divisor); 662 663 // "Round half to even" division rounds both positive and negative quotients 664 // to the nearest even integer. See [1]. 665 // 666 // Int128 division truncates, so we implicitly round toward zero. When the 667 // fractional part of the remainder is 0.5 and the quotient is odd or when the 668 // fractional part of the remainder is >0.5, we should instead have rounded 669 // toward ±infinity, so we need to increment resp. decrement the quotient by 670 // one. 671 // 672 // [1] 673 // https://tc39.es/proposal-temporal/#table-temporal-unsigned-rounding-modes 674 if ((quotient & Int128{1}) == Int128{1} && 675 Uint128(remainder.abs()) * Uint128{2} == static_cast<Uint128>(divisor)) { 676 quotient += (dividend > Int128{0}) ? Int128{1} : Int128{-1}; 677 } 678 if (Uint128(remainder.abs()) * Uint128{2} > static_cast<Uint128>(divisor)) { 679 quotient += (dividend > Int128{0}) ? Int128{1} : Int128{-1}; 680 } 681 return quotient; 682 } 683 684 /** 685 * Perform `dividend / divisor` and round the result according to the given 686 * rounding mode. 687 */ 688 inline Int128 Divide(const Int128& dividend, const Int128& divisor, 689 TemporalRoundingMode roundingMode) { 690 switch (roundingMode) { 691 case TemporalRoundingMode::Ceil: 692 return CeilDiv(dividend, divisor); 693 case TemporalRoundingMode::Floor: 694 return FloorDiv(dividend, divisor); 695 case TemporalRoundingMode::Expand: 696 return ExpandDiv(dividend, divisor); 697 case TemporalRoundingMode::Trunc: 698 return TruncDiv(dividend, divisor); 699 case TemporalRoundingMode::HalfCeil: 700 return HalfCeilDiv(dividend, divisor); 701 case TemporalRoundingMode::HalfFloor: 702 return HalfFloorDiv(dividend, divisor); 703 case TemporalRoundingMode::HalfExpand: 704 return HalfExpandDiv(dividend, divisor); 705 case TemporalRoundingMode::HalfTrunc: 706 return HalfTruncDiv(dividend, divisor); 707 case TemporalRoundingMode::HalfEven: 708 return HalfEvenDiv(dividend, divisor); 709 } 710 MOZ_CRASH("invalid rounding mode"); 711 } 712 713 } /* namespace js::temporal */ 714 715 #endif /* builtin_temporal_TemporalRoundingMode_h */