001/* 002 * The MIT License (MIT) 003 * 004 * Copyright (c) 2015-2023 decimal4j (tools4j), Marco Terzer 005 * 006 * Permission is hereby granted, free of charge, to any person obtaining a copy 007 * of this software and associated documentation files (the "Software"), to deal 008 * in the Software without restriction, including without limitation the rights 009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 010 * copies of the Software, and to permit persons to whom the Software is 011 * furnished to do so, subject to the following conditions: 012 * 013 * The above copyright notice and this permission notice shall be included in all 014 * copies or substantial portions of the Software. 015 * 016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 022 * SOFTWARE. 023 */ 024package org.decimal4j.arithmetic; 025 026import org.decimal4j.api.DecimalArithmetic; 027import org.decimal4j.scale.Scale18f; 028import org.decimal4j.scale.ScaleMetrics; 029import org.decimal4j.scale.Scales; 030import org.decimal4j.truncate.DecimalRounding; 031import org.decimal4j.truncate.TruncatedPart; 032 033/** 034 * Contains methods for multiplications and divisions with powers of ten. 035 */ 036final class Pow10 { 037 038 /** 039 * Calculates the multiple by a power of 10 truncating the result if 040 * necessary for negative {@code n}. Overflows are silently truncated. 041 * 042 * @param uDecimal 043 * the value to multiply 044 * @param n 045 * the power-ten exponent 046 * @return <code>round<sub>DOWN</sub>(uDecimal * 10<sup>n</sup>)</code> 047 */ 048 public static final long multiplyByPowerOf10(final long uDecimal, final int n) { 049 if (uDecimal == 0 | n == 0) { 050 return uDecimal; 051 } 052 if (n > 0) { 053 int pos = n; 054 long result = uDecimal; 055 // NOTE: result will be 0 after at most 1+64/18 rounds 056 // because 10^64 contains 2^64 which is a shift left by 64 057 while (pos > 18) { 058 result = Scale18f.INSTANCE.multiplyByScaleFactor(result); 059 if (result == 0) { 060 return 0; 061 } 062 pos -= 18; 063 } 064 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(pos); 065 return scaleMetrics.multiplyByScaleFactor(result); 066 } else { 067 if (n >= -18) { 068 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(-n); 069 return scaleMetrics.divideByScaleFactor(uDecimal); 070 } 071 // truncated result is 0 072 return 0; 073 } 074 } 075 076 /** 077 * Calculates the multiple by a power of 10 rounding the result if necessary 078 * for negative {@code n}. Overflows are silently truncated. 079 * 080 * @param rounding 081 * the rounding to apply if necessary 082 * @param uDecimal 083 * the value to multiply 084 * @param n 085 * the power-ten exponent 086 * @return <code>round(uDecimal * 10<sup>n</sup>)</code> 087 */ 088 public static final long multiplyByPowerOf10(final DecimalRounding rounding, final long uDecimal, final int n) { 089 if (uDecimal == 0 | n == 0) { 090 return uDecimal; 091 } 092 if (n > 0) { 093 int pos = n; 094 long result = uDecimal; 095 // NOTE: result will be 0 after at most 1+64/18 rounds 096 // because 10^64 contains 2^64 which is a shift left by 64 097 while (pos > 18) { 098 result = Scale18f.INSTANCE.multiplyByScaleFactor(result); 099 if (result == 0) { 100 return 0; 101 } 102 pos -= 18; 103 } 104 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(pos); 105 return scaleMetrics.multiplyByScaleFactor(result); 106 } else { 107 if (n >= -18) { 108 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(-n); 109 final long truncated = scaleMetrics.divideByScaleFactor(uDecimal); 110 final long rem = uDecimal - scaleMetrics.multiplyByScaleFactor(truncated); 111 final long inc = Rounding.calculateRoundingIncrement(rounding, truncated, rem, 112 scaleMetrics.getScaleFactor()); 113 return truncated + inc; 114 } else if (n == -19) { 115 return rounding.calculateRoundingIncrement(Long.signum(uDecimal), 0, 116 Rounding.truncatedPartForScale19(uDecimal)); 117 } 118 // truncated part is always larger 0 (see first if) 119 // and less than 0.5 because abs(Long.MIN_VALUE) / 10^20 < 0.5 120 return rounding.calculateRoundingIncrement(Long.signum(uDecimal), 0, 121 TruncatedPart.LESS_THAN_HALF_BUT_NOT_ZERO); 122 } 123 } 124 125 /** 126 * Calculates the multiple by a power of 10 truncating the result if 127 * necessary for negative {@code n}. An exception is thrown if an overflow 128 * occurs. 129 * 130 * @param arith 131 * the arithmetic associated with the value 132 * @param uDecimal 133 * the value to multiply 134 * @param n 135 * the power-ten exponent 136 * @return <code>round<sub>DOWN</sub>(uDecimal * 10<sup>n</sup>)</code> 137 * @throws ArithmeticException 138 * if an overflow occurs and the arithmetic's 139 * {@code OverflowMode} is set to throw an exception 140 */ 141 public static final long multiplyByPowerOf10Checked(final DecimalArithmetic arith, final long uDecimal, final int n) { 142 if (uDecimal == 0 | n == 0) { 143 return uDecimal; 144 } 145 146 if (n > 0) { 147 if (n > 18) { 148 throw new ArithmeticException("Overflow: " + arith.toString(uDecimal) + " * 10^" + n); 149 } 150 151 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(n); 152 return scaleMetrics.multiplyByScaleFactorExact(uDecimal); 153 } else { 154 if (n >= -18) { 155 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(-n); 156 return scaleMetrics.divideByScaleFactor(uDecimal); 157 } 158 return 0; 159 } 160 } 161 162 /** 163 * Calculates the multiple by a power of 10 rounding the result if necessary 164 * for negative {@code n}. An exception is thrown if an overflow occurs. 165 * 166 * @param arith 167 * the arithmetic associated with the value 168 * @param rounding 169 * the rounding to apply if necessary 170 * @param uDecimal 171 * the value to multiply 172 * @param n 173 * the power-ten exponent 174 * @return <code>round(uDecimal * 10<sup>n</sup>)</code> 175 * @throws ArithmeticException 176 * if an overflow occurs and the arithmetic's 177 * {@code OverflowMode} is set to throw an exception 178 */ 179 public static final long multiplyByPowerOf10Checked(final DecimalArithmetic arith, final DecimalRounding rounding, final long uDecimal, final int n) { 180 if (uDecimal == 0 | n == 0) { 181 return uDecimal; 182 } 183 184 if (rounding == DecimalRounding.DOWN) { 185 return multiplyByPowerOf10Checked(arith, uDecimal, Math.abs(n)); 186 } 187 188 if (n > 0) { 189 if (n > 18) { 190 throw new ArithmeticException("Overflow: " + arith.toString(uDecimal) + " * 10^" + n); 191 } 192 193 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(n); 194 return scaleMetrics.multiplyByScaleFactorExact(uDecimal); 195 } else { 196 if (n >= -18) { 197 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(-n); 198 final long truncated = scaleMetrics.divideByScaleFactor(uDecimal); 199 final long rem = uDecimal - scaleMetrics.multiplyByScaleFactor(truncated); 200 final long inc = Rounding.calculateRoundingIncrement(rounding, truncated, rem, 201 scaleMetrics.getScaleFactor()); 202 return truncated + inc; 203 } else if (n == -19) { 204 return rounding.calculateRoundingIncrement(Long.signum(uDecimal), 0, 205 Rounding.truncatedPartForScale19(uDecimal)); 206 } 207 // truncated part is always larger 0 (see first if) 208 // and less than 0.5 because abs(Long.MIN_VALUE) / 10^20 < 0.5 209 return rounding.calculateRoundingIncrement(Long.signum(uDecimal), 0, 210 TruncatedPart.LESS_THAN_HALF_BUT_NOT_ZERO); 211 } 212 } 213 214 /** 215 * Divides the given value by a power of 10 truncating the result if 216 * necessary. Overflows are silently truncated. 217 * 218 * @param uDecimal 219 * the value to divide 220 * @param n 221 * the power-ten exponent 222 * @return <code>round<sub>DOWN</sub>(uDecimal / 10<sup>n</sup>)</code> 223 */ 224 public static final long divideByPowerOf10(final long uDecimal, final int n) { 225 if (uDecimal == 0 | n == 0) { 226 return uDecimal; 227 } 228 229 if (n > 0) { 230 if (n > 18) { 231 return 0; // truncated result is 0 232 } 233 234 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(n); 235 return scaleMetrics.divideByScaleFactor(uDecimal); 236 } else { 237 int pos = n; 238 long result = uDecimal; 239 // NOTE: result will be 0 after at most 1+64/18 rounds 240 // because 10^64 contains 2^64 which is a shift left by 64 241 while (pos < -18) { 242 result = Scale18f.INSTANCE.multiplyByScaleFactor(result); 243 if (result == 0) { 244 return 0; 245 } 246 pos += 18; 247 } 248 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(-pos); 249 return scaleMetrics.multiplyByScaleFactor(result); 250 } 251 } 252 253 /** 254 * Divides the given value by a power of 10 rounding the result if 255 * necessary. Overflows are silently truncated. 256 * 257 * @param rounding 258 * the rounding to apply if necessary 259 * @param uDecimal 260 * the value to divide 261 * @param n 262 * the power-ten exponent 263 * @return <code>round(uDecimal / 10<sup>n</sup>)</code> 264 */ 265 public static final long divideByPowerOf10(final DecimalRounding rounding, final long uDecimal, final int n) { 266 if (uDecimal == 0 | n == 0) { 267 return uDecimal; 268 } 269 270 if (rounding == DecimalRounding.DOWN) { 271 return divideByPowerOf10(uDecimal, n); 272 } 273 274 if (n > 0) { 275 if (n > 19) { 276 // truncated part is always larger 0 (see first if) 277 // and less than 0.5 because abs(Long.MIN_VALUE) / 10^20 < 0.5 278 return rounding.calculateRoundingIncrement(Long.signum(uDecimal), 0, 279 TruncatedPart.LESS_THAN_HALF_BUT_NOT_ZERO); 280 } else if (n == 19) { 281 return rounding.calculateRoundingIncrement(Long.signum(uDecimal), 0, 282 Rounding.truncatedPartForScale19(uDecimal)); 283 } 284 285 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(n); 286 final long truncated = scaleMetrics.divideByScaleFactor(uDecimal); 287 final long rem = uDecimal - scaleMetrics.multiplyByScaleFactor(truncated); 288 final long inc = Rounding.calculateRoundingIncrement(rounding, truncated, rem, 289 scaleMetrics.getScaleFactor()); 290 return truncated + inc; 291 } else { 292 int pos = n; 293 long result = uDecimal; 294 // NOTE: result will be 0 after at most 1+64/18 rounds 295 // because 10^64 contains 2^64 which is a shift left by 64 296 while (pos < -18) { 297 result = Scale18f.INSTANCE.multiplyByScaleFactor(result); 298 if (result == 0) { 299 return 0; 300 } 301 pos += 18; 302 } 303 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(-pos); 304 return scaleMetrics.multiplyByScaleFactor(result); 305 } 306 } 307 308 /** 309 * Divides the given value by a power of 10 truncating the result if 310 * necessary. An exception is thrown if an overflow occurs. 311 * 312 * @param arith 313 * the arithmetic associated with the value 314 * @param uDecimal 315 * the value to divide 316 * @param n 317 * the power-ten exponent 318 * @return <code>round<sub>DOWN</sub>(uDecimal / 10<sup>n</sup>)</code> 319 * @throws ArithmeticException 320 * if an overflow occurs and the arithmetic's 321 * {@code OverflowMode} is set to throw an exception 322 */ 323 public static final long divideByPowerOf10Checked(final DecimalArithmetic arith, final long uDecimal, final int n) { 324 if (uDecimal == 0 | n == 0) { 325 return uDecimal; 326 } 327 328 if (n > 0) { 329 if (n > 18) { 330 return 0; 331 } 332 333 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(n); 334 return scaleMetrics.divideByScaleFactor(uDecimal); 335 } else { 336 if (n >= -18) { 337 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(-n); 338 return scaleMetrics.multiplyByScaleFactorExact(uDecimal); 339 } 340 throw new ArithmeticException("Overflow: " + arith.toString(uDecimal) + " / 10^" + n); 341 } 342 } 343 344 /** 345 * Divides the given value by a power of 10 rounding the result if 346 * necessary. An exception is thrown if an overflow occurs. 347 * 348 * @param arith 349 * the arithmetic associated with the value 350 * @param rounding 351 * the rounding to apply if necessary 352 * @param uDecimal 353 * the value to divide 354 * @param n 355 * the power-ten exponent 356 * @return <code>round(uDecimal / 10<sup>n</sup>)</code> 357 * @throws ArithmeticException 358 * if an overflow occurs and the arithmetic's 359 * {@code OverflowMode} is set to throw an exception 360 */ 361 public static final long divideByPowerOf10Checked(final DecimalArithmetic arith, final DecimalRounding rounding, final long uDecimal, final int n) { 362 if (uDecimal == 0 | n == 0) { 363 return uDecimal; 364 } 365 366 if (rounding == DecimalRounding.DOWN) { 367 return divideByPowerOf10Checked(arith, uDecimal, Math.abs(n)); 368 } 369 370 if (n > 0) { 371 if (n > 19) { 372 // truncated part is always larger 0 (see first if) 373 // and less than 0.5 because abs(Long.MIN_VALUE) / 10^20 < 0.5 374 return rounding.calculateRoundingIncrement(Long.signum(uDecimal), 0, 375 TruncatedPart.LESS_THAN_HALF_BUT_NOT_ZERO); 376 } else if (n == 19) { 377 return rounding.calculateRoundingIncrement(Long.signum(uDecimal), 0, 378 Rounding.truncatedPartForScale19(uDecimal)); 379 } 380 381 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(n); 382 final long truncated = scaleMetrics.divideByScaleFactor(uDecimal); 383 final long rem = uDecimal - scaleMetrics.multiplyByScaleFactor(truncated); 384 final long inc = Rounding.calculateRoundingIncrement(rounding, truncated, rem, 385 scaleMetrics.getScaleFactor()); 386 return truncated + inc; 387 } else { 388 if (n < -18) { 389 throw new ArithmeticException("Overflow: " + arith.toString(uDecimal) + " / 10^" + n); 390 } 391 392 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(-n); 393 return scaleMetrics.multiplyByScaleFactorExact(uDecimal); 394 } 395 } 396 397 /** 398 * Divides the specified dividend by a power of ten truncating the result if 399 * necessary. Overflows are silently truncated. 400 * 401 * @param uDecimalDividend 402 * the dividend to divide 403 * @param dividendMetrics 404 * the arithmetics associated with the dividend 405 * @param pow10divisorIsPositive 406 * true if the divisor is positive 407 * @param pow10divisorMetrics 408 * the metrics reflecting the power-ten-division 409 * @return <code>round<sub>DOWN</sub>(uDecimalDividend / 10<sup>(scale - scale10)</sup>)</code> 410 * where scale is the {@code scale} of the dividend and 411 * {@code scale10} is the exponent of the power-ten divisor (negated 412 * if {@code pow10divisorIsPositive==false}) 413 */ 414 static final long divideByPowerOf10(final long uDecimalDividend, final ScaleMetrics dividendMetrics, final boolean pow10divisorIsPositive, final ScaleMetrics pow10divisorMetrics) { 415 final int scaleDiff = dividendMetrics.getScale() - pow10divisorMetrics.getScale(); 416 final long quot; 417 if (scaleDiff <= 0) { 418 // divide 419 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(-scaleDiff); 420 quot = scaleMetrics.divideByScaleFactor(uDecimalDividend); 421 422 } else { 423 // multiply 424 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(scaleDiff); 425 quot = scaleMetrics.multiplyByScaleFactor(uDecimalDividend); 426 } 427 return pow10divisorIsPositive ? quot : -quot; 428 } 429 430 /** 431 * Divides the specified dividend by a power of ten rounding the result if 432 * necessary. Overflows are silently truncated. 433 * 434 * @param rounding 435 * the rounding to apply if rounding is necessary 436 * @param uDecimalDividend 437 * the dividend to divide 438 * @param dividendMetrics 439 * the arithmetics associated with the dividend 440 * @param pow10divisorIsPositive 441 * true if the divisor is positive 442 * @param pow10divisorMetrics 443 * the metrics reflecting the power-ten-division 444 * @return <code>round(uDecimalDividend / 10<sup>(scale - scale10)</sup>)</code> 445 * where scale is the {@code scale} of the dividend and 446 * {@code scale10} is the exponent of the power-ten divisor (negated 447 * if {@code pow10divisorIsPositive==false}) 448 */ 449 static final long divideByPowerOf10(final DecimalRounding rounding, final long uDecimalDividend, final ScaleMetrics dividendMetrics, final boolean pow10divisorIsPositive, final ScaleMetrics pow10divisorMetrics) { 450 final int scaleDiff = dividendMetrics.getScale() - pow10divisorMetrics.getScale(); 451 if (scaleDiff <= 0) { 452 // divide 453 final ScaleMetrics scaler = Scales.getScaleMetrics(-scaleDiff); 454 final long truncatedValue = scaler.divideByScaleFactor(uDecimalDividend); 455 final long truncatedDigits = uDecimalDividend - scaler.multiplyByScaleFactor(truncatedValue); 456 if (pow10divisorIsPositive) { 457 return truncatedValue + Rounding.calculateRoundingIncrementForDivision(rounding, truncatedValue, 458 truncatedDigits, scaler.getScaleFactor()); 459 } 460 return -truncatedValue + Rounding.calculateRoundingIncrementForDivision(rounding, -truncatedValue, 461 -truncatedDigits, scaler.getScaleFactor()); 462 } else { 463 // multiply 464 final ScaleMetrics scaler = Scales.getScaleMetrics(scaleDiff); 465 final long quot = scaler.multiplyByScaleFactor(uDecimalDividend); 466 return pow10divisorIsPositive ? quot : -quot; 467 } 468 } 469 470 /** 471 * Divides the specified dividend by a power of ten truncating the result if 472 * necessary. An exception is thrown if an overflow occurs. 473 * 474 * @param arith 475 * the arithmetic associated with the dividend value 476 * @param uDecimalDividend 477 * the dividend to divide 478 * @param dividendMetrics 479 * the arithmetics associated with the dividend 480 * @param pow10divisorIsPositive 481 * true if the divisor is positive 482 * @param pow10divisorMetrics 483 * the metrics reflecting the power-ten-division 484 * @return <code>round<sub>DOWN</sub>(uDecimalDividend / 10<sup>(scale - scale10)</sup>)</code> 485 * where scale is the {@code scale} of the dividend and 486 * {@code scale10} is the exponent of the power-ten divisor (negated 487 * if {@code pow10divisorIsPositive==false}) 488 * @throws ArithmeticException 489 * if an overflow occurs and the arithmetic's 490 * {@code OverflowMode} is set to throw an exception 491 */ 492 static final long divideByPowerOf10Checked(final DecimalArithmetic arith, final long uDecimalDividend, final ScaleMetrics dividendMetrics, final boolean pow10divisorIsPositive, final ScaleMetrics pow10divisorMetrics) { 493 final int scaleDiff = dividendMetrics.getScale() - pow10divisorMetrics.getScale(); 494 final long quot; 495 if (scaleDiff <= 0) { 496 // divide 497 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(-scaleDiff); 498 quot = scaleMetrics.divideByScaleFactor(uDecimalDividend); 499 } else { 500 // multiply 501 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(scaleDiff); 502 quot = scaleMetrics.multiplyByScaleFactorExact(uDecimalDividend); 503 } 504 return pow10divisorIsPositive ? quot : arith.negate(quot); 505 } 506 507 /** 508 * Divides the specified dividend by a power of ten rounding the result if 509 * necessary. An exception is thrown if an overflow occurs. 510 * 511 * @param arith 512 * the arithmetic associated with the dividend value 513 * @param rounding 514 * the rounding to apply if rounding is necessary 515 * @param uDecimalDividend 516 * the dividend to divide 517 * @param dividendMetrics 518 * the arithmetics associated with the dividend 519 * @param pow10divisorIsPositive 520 * true if the divisor is positive 521 * @param pow10divisorMetrics 522 * the metrics reflecting the power-ten-division 523 * @return <code>round(uDecimalDividend / 10<sup>(scale - scale10)</sup>)</code> 524 * where scale is the {@code scale} of the dividend and 525 * {@code scale10} is the exponent of the power-ten divisor (negated 526 * if {@code pow10divisorIsPositive==false}) 527 * @throws ArithmeticException 528 * if an overflow occurs and the arithmetic's 529 * {@code OverflowMode} is set to throw an exception 530 */ 531 static final long divideByPowerOf10Checked(final DecimalArithmetic arith, final DecimalRounding rounding, final long uDecimalDividend, final ScaleMetrics dividendMetrics, final boolean pow10divisorIsPositive, final ScaleMetrics pow10divisorMetrics) { 532 final int scaleDiff = dividendMetrics.getScale() - pow10divisorMetrics.getScale(); 533 final long quot; 534 if (scaleDiff <= 0) { 535 // divide 536 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(-scaleDiff); 537 quot = scaleMetrics.divideByScaleFactor(uDecimalDividend); 538 539 final long truncatedDigits = uDecimalDividend - scaleMetrics.multiplyByScaleFactor(quot); 540 if (pow10divisorIsPositive) { 541 return quot + Rounding.calculateRoundingIncrementForDivision(rounding, quot, truncatedDigits, 542 scaleMetrics.getScaleFactor()); 543 } 544 return -quot + Rounding.calculateRoundingIncrementForDivision(rounding, -quot, -truncatedDigits, 545 scaleMetrics.getScaleFactor()); 546 } else { 547 // multiply 548 final ScaleMetrics scaleMetrics = Scales.getScaleMetrics(scaleDiff); 549 quot = scaleMetrics.multiplyByScaleFactorExact(uDecimalDividend); 550 } 551 return pow10divisorIsPositive ? quot : arith.negate(quot); 552 } 553 554 // no instances 555 private Pow10() { 556 } 557 558}