@@ -1126,3 +1126,224 @@ export const isNegative = (n: BigDecimal): boolean => n.value < bigint0
1126
1126
* @category predicates
1127
1127
*/
1128
1128
export const isPositive = ( n : BigDecimal ) : boolean => n . value > bigint0
1129
+
1130
+ const isBigDecimalArgs = ( args : IArguments ) => isBigDecimal ( args [ 0 ] )
1131
+
1132
+ /**
1133
+ * Calculate the ceiling of a `BigDecimal` at the given scale.
1134
+ *
1135
+ * @example
1136
+ * ```ts
1137
+ * import * as assert from "node:assert"
1138
+ * import { ceil, unsafeFromString } from "effect/BigDecimal"
1139
+ *
1140
+ * assert.deepStrictEqual(ceil(unsafeFromString("145"), -1), unsafeFromString("150"))
1141
+ * assert.deepStrictEqual(ceil(unsafeFromString("-14.5")), unsafeFromString("-14"))
1142
+ * ```
1143
+ *
1144
+ * @since 3.16.0
1145
+ * @category math
1146
+ */
1147
+ export const ceil : {
1148
+ ( scale : number ) : ( self : BigDecimal ) => BigDecimal
1149
+ ( self : BigDecimal , scale ?: number ) : BigDecimal
1150
+ } = dual ( isBigDecimalArgs , ( self : BigDecimal , scale : number = 0 ) : BigDecimal => {
1151
+ const truncated = truncate ( self , scale )
1152
+
1153
+ if ( isPositive ( self ) && lessThan ( truncated , self ) ) {
1154
+ return sum ( truncated , make ( 1n , scale ) )
1155
+ }
1156
+
1157
+ return truncated
1158
+ } )
1159
+
1160
+ /**
1161
+ * Calculate the floor of a `BigDecimal` at the given scale.
1162
+ *
1163
+ * @example
1164
+ * ```ts
1165
+ * import * as assert from "node:assert"
1166
+ * import { floor, unsafeFromString } from "effect/BigDecimal"
1167
+ *
1168
+ * assert.deepStrictEqual(floor(unsafeFromString("145"), -1), unsafeFromString("140"))
1169
+ * assert.deepStrictEqual(floor(unsafeFromString("-14.5")), unsafeFromString("-15"))
1170
+ * ```
1171
+ *
1172
+ * @since 3.16.0
1173
+ * @category math
1174
+ */
1175
+ export const floor : {
1176
+ ( scale : number ) : ( self : BigDecimal ) => BigDecimal
1177
+ ( self : BigDecimal , scale ?: number ) : BigDecimal
1178
+ } = dual ( isBigDecimalArgs , ( self : BigDecimal , scale : number = 0 ) : BigDecimal => {
1179
+ const truncated = truncate ( self , scale )
1180
+
1181
+ if ( isNegative ( self ) && greaterThan ( truncated , self ) ) {
1182
+ return sum ( truncated , make ( - 1n , scale ) )
1183
+ }
1184
+
1185
+ return truncated
1186
+ } )
1187
+
1188
+ /**
1189
+ * Truncate a `BigDecimal` at the given scale. This is the same operation as rounding away from zero.
1190
+ *
1191
+ * @example
1192
+ * ```ts
1193
+ * import * as assert from "node:assert"
1194
+ * import { truncate, unsafeFromString } from "effect/BigDecimal"
1195
+ *
1196
+ * assert.deepStrictEqual(truncate(unsafeFromString("145"), -1), unsafeFromString("140"))
1197
+ * assert.deepStrictEqual(truncate(unsafeFromString("-14.5")), unsafeFromString("-14"))
1198
+ * ```
1199
+ *
1200
+ * @since 3.16.0
1201
+ * @category math
1202
+ */
1203
+ export const truncate : {
1204
+ ( scale : number ) : ( self : BigDecimal ) => BigDecimal
1205
+ ( self : BigDecimal , scale ?: number ) : BigDecimal
1206
+ } = dual ( isBigDecimalArgs , ( self : BigDecimal , scale : number = 0 ) : BigDecimal => {
1207
+ if ( self . scale <= scale ) {
1208
+ return self
1209
+ }
1210
+
1211
+ // BigInt division truncates towards zero
1212
+ return make ( self . value / ( 10n ** BigInt ( self . scale - scale ) ) , scale )
1213
+ } )
1214
+
1215
+ /**
1216
+ * Internal function used by `round` for `half-even` and `half-odd` rounding modes.
1217
+ *
1218
+ * Returns the digit at the position of the given `scale` within the `BigDecimal`.
1219
+ *
1220
+ * @internal
1221
+ */
1222
+ export const digitAt : {
1223
+ ( scale : number ) : ( self : BigDecimal ) => bigint
1224
+ ( self : BigDecimal , scale : number ) : bigint
1225
+ } = dual ( 2 , ( self : BigDecimal , scale : number ) : bigint => {
1226
+ if ( self . scale < scale ) {
1227
+ return 0n
1228
+ }
1229
+
1230
+ const scaled = self . value / ( 10n ** BigInt ( self . scale - scale ) )
1231
+ return scaled % 10n
1232
+ } )
1233
+
1234
+ /**
1235
+ * Rounding modes for `BigDecimal`.
1236
+ *
1237
+ * `ceil`: round towards positive infinity
1238
+ * `floor`: round towards negative infinity
1239
+ * `to-zero`: round towards zero
1240
+ * `from-zero`: round away from zero
1241
+ * `half-ceil`: round to the nearest neighbor; if equidistant round towards positive infinity
1242
+ * `half-floor`: round to the nearest neighbor; if equidistant round towards negative infinity
1243
+ * `half-to-zero`: round to the nearest neighbor; if equidistant round towards zero
1244
+ * `half-from-zero`: round to the nearest neighbor; if equidistant round away from zero
1245
+ * `half-even`: round to the nearest neighbor; if equidistant round to the neighbor with an even digit
1246
+ * `half-odd`: round to the nearest neighbor; if equidistant round to the neighbor with an odd digit
1247
+ *
1248
+ * @since 3.16.0
1249
+ * @category math
1250
+ */
1251
+ export type RoundingMode =
1252
+ | "ceil"
1253
+ | "floor"
1254
+ | "to-zero"
1255
+ | "from-zero"
1256
+ | "half-ceil"
1257
+ | "half-floor"
1258
+ | "half-to-zero"
1259
+ | "half-from-zero"
1260
+ | "half-even"
1261
+ | "half-odd"
1262
+
1263
+ /**
1264
+ * Rounds a `BigDecimal` at the given scale with the specified rounding mode.
1265
+ *
1266
+ * @example
1267
+ * ```ts
1268
+ * import * as assert from "node:assert"
1269
+ * import { round, unsafeFromString } from "effect/BigDecimal"
1270
+ *
1271
+ * assert.deepStrictEqual(round(unsafeFromString("145"), { mode: "from-zero", scale: -1 }), unsafeFromString("150"))
1272
+ * assert.deepStrictEqual(round(unsafeFromString("-14.5")), unsafeFromString("-15"))
1273
+ * ```
1274
+ *
1275
+ * @since 3.16.0
1276
+ * @category math
1277
+ */
1278
+ export const round : {
1279
+ ( options : { scale ?: number ; mode ?: RoundingMode } ) : ( self : BigDecimal ) => BigDecimal
1280
+ ( n : BigDecimal , options ?: { scale ?: number ; mode ?: RoundingMode } ) : BigDecimal
1281
+ } = dual ( isBigDecimalArgs , ( self : BigDecimal , options ?: { scale ?: number ; mode ?: RoundingMode } ) : BigDecimal => {
1282
+ const mode = options ?. mode ?? "half-from-zero"
1283
+ const scale = options ?. scale ?? 0
1284
+
1285
+ switch ( mode ) {
1286
+ case "ceil" :
1287
+ return ceil ( self , scale )
1288
+
1289
+ case "floor" :
1290
+ return floor ( self , scale )
1291
+
1292
+ case "to-zero" :
1293
+ return truncate ( self , scale )
1294
+
1295
+ case "from-zero" :
1296
+ return ( isPositive ( self ) ? ceil ( self , scale ) : floor ( self , scale ) )
1297
+
1298
+ case "half-ceil" :
1299
+ return floor ( sum ( self , make ( 5n , scale + 1 ) ) , scale )
1300
+
1301
+ case "half-floor" :
1302
+ return ceil ( sum ( self , make ( - 5n , scale + 1 ) ) , scale )
1303
+
1304
+ case "half-to-zero" :
1305
+ return isNegative ( self )
1306
+ ? floor ( sum ( self , make ( 5n , scale + 1 ) ) , scale )
1307
+ : ceil ( sum ( self , make ( - 5n , scale + 1 ) ) , scale )
1308
+
1309
+ case "half-from-zero" :
1310
+ return isNegative ( self )
1311
+ ? ceil ( sum ( self , make ( - 5n , scale + 1 ) ) , scale )
1312
+ : floor ( sum ( self , make ( 5n , scale + 1 ) ) , scale )
1313
+ }
1314
+
1315
+ const halfCeil = floor ( sum ( self , make ( 5n , scale + 1 ) ) , scale )
1316
+ const halfFloor = ceil ( sum ( self , make ( - 5n , scale + 1 ) ) , scale )
1317
+ const digit = digitAt ( halfCeil , scale )
1318
+
1319
+ switch ( mode ) {
1320
+ case "half-even" :
1321
+ return equals ( halfCeil , halfFloor ) ? halfCeil : ( digit % 2n === 0n ) ? halfCeil : halfFloor
1322
+
1323
+ case "half-odd" :
1324
+ return equals ( halfCeil , halfFloor ) ? halfCeil : ( digit % 2n === 0n ) ? halfFloor : halfCeil
1325
+ }
1326
+ } )
1327
+
1328
+ /**
1329
+ * Takes an `Iterable` of `BigDecimal`s and returns their sum as a single `BigDecimal`
1330
+ *
1331
+ * @example
1332
+ * ```ts
1333
+ * import * as assert from "node:assert"
1334
+ * import { unsafeFromString, sumAll } from "effect/BigDecimal"
1335
+ *
1336
+ * assert.deepStrictEqual(sumAll([unsafeFromString("2"), unsafeFromString("3"), unsafeFromString("4")]), unsafeFromString("9"))
1337
+ * ```
1338
+ *
1339
+ * @category math
1340
+ * @since 3.16.0
1341
+ */
1342
+ export const sumAll = ( collection : Iterable < BigDecimal > ) : BigDecimal => {
1343
+ let out = zero
1344
+ for ( const n of collection ) {
1345
+ out = sum ( out , n )
1346
+ }
1347
+
1348
+ return out
1349
+ }
0 commit comments