Java中BigDecimal最大范围与最小范围取随机数
时间: 2023-06-04 14:08:11 浏览: 1568
好的,关于Java中BigDecimal最大范围与最小范围取随机数的问题,我可以给出以下回答:
Java中BigDecimal类的最大值和最小值取决于BigDecimal的scale和precision属性。在Java中,可以通过BigDecimal类的构造方法来设定这些属性的值。如果需要生成一个指定范围内的随机数,我们可以通过先设定BigDecimal的值区间,再生成一个随机数来实现。
例如,如果我们需要生成一个介于1和100之间的随机小数,可以先使用BigDecimal的setScale()方法来设置小数点后的精度,然后使用BigDecimal的multiply()方法生成一个0-100之间的随机数,最后再加上1就可以得到所需的随机小数了。代码示例如下:
```
BigDecimal low = new BigDecimal("1");
BigDecimal high = new BigDecimal("100");
BigDecimal range = high.subtract(low);
BigDecimal randomBigDecimal = range.multiply(new BigDecimal(Math.random())).add(low);
```
需要注意的是,由于BigDecimal的计算操作比较复杂,因此在实际使用中应该尽量避免频繁创建和销毁BigDecimal对象,而采用复用和缓存的方式来提高性能。
相关问题
java 如何给随机数增加权重
<think>好的,用户问的是如何在Java中实现带权重的随机数算法。首先,我需要回忆一下相关的知识。带权重的随机数,应该是根据不同的权重值,让每个选项有不同的被选中的概率。常见的方法有线性扫描、前缀和加二分查找,或者使用TreeMap这样的数据结构。
用户之前提供的引用里提到了TreeMap的实现,还有前缀和和二分查找的优化方法。比如引用[3]中提到的银行抽奖例子,使用TreeMap来存储累积权重,然后利用ceilingKey方法查找随机数对应的奖品。这可能是一个关键点。
首先,我应该考虑基本的实现方法。线性扫描虽然简单,但在选项很多时效率不高。比如,假设有三个选项,权重分别是1、2、3,总权重是6。生成一个0到6之间的随机数,然后依次减去每个权重,直到找到对应的区间。这种方法的时间复杂度是O(n),对于大量数据来说可能不够高效。
接下来是优化方法,比如前缀和数组配合二分查找。这种方法需要预先计算前缀和数组,然后生成随机数后,用二分查找找到对应的位置,这样时间复杂度可以降到O(log n)。这在引用[1]中也提到过,处理大量选项时应考虑这种优化。
另外,TreeMap的实现方式可能更高效,因为TreeMap的ceilingKey方法本身就是基于红黑树实现的,查找效率是O(log n)。这在引用[3]的例子中被使用,所以需要详细说明如何利用TreeMap来存储累积权重,并找到对应的键。
然后,我需要考虑如何将这些方法用Java代码实现。比如,线性扫描的具体步骤:计算总权重,生成随机数,遍历每个元素减去权重直到找到负数。代码示例应该清晰,用户可以直接复制使用。
对于前缀和和二分查找的方法,需要先计算前缀和数组,然后使用Arrays.binarySearch方法。这里需要注意,如果随机数不在数组中,binarySearch会返回插入点,需要处理负数的情况。
TreeMap的方法则需要将累积权重作为key,对应的对象作为value,然后使用ceilingKey来找到最小的键大于等于随机数。这个方法可能代码更简洁,但需要理解TreeMap的特性。
还要考虑权重为0的情况,或者权重总和为0的情况,这时候可能需要异常处理。此外,权重是否可以是浮点数,或者必须是整数?根据引用中的例子,权重可以是double类型,所以代码中应该支持浮点数。
用户可能的应用场景,比如游戏抽奖、负载均衡、随机推荐等,需要简要提及。同时,性能优化的部分需要强调在不同数据量下的选择,比如小数据量用线性扫描,大数据量用二分或TreeMap。
最后,生成相关问题,可能涉及性能分析、优化方法、应用场景或者其他实现方式,比如随机池抽样等。需要确保问题与主题相关,帮助用户深入理解。</think>### Java实现带权重随机数算法详解
#### 一、核心实现原理
带权重随机算法的核心是**概率空间映射**。假设有$n$个元素,每个元素权重为$w_i$,总权重$W=\sum_{w_i}$,算法通过以下步骤实现:
1. 构建累积概率分布
2. 生成[0,W)区间的随机数
3. 根据随机数定位对应元素
#### 二、基础线性扫描法
```java
public class WeightedRandom {
private List<Double> weights = new ArrayList<>();
private double totalWeight;
public void addItem(double weight) {
weights.add(weight);
totalWeight += weight;
}
public int nextIndex() {
double random = Math.random() * totalWeight;
for (int i = 0; i < weights.size(); i++) {
random -= weights.get(i);
if (random < 0) {
return i;
}
}
return weights.size() - 1;
}
}
```
时间复杂度:$O(n)$,适用于小型数据集[^3]
#### 三、优化方案:前缀和+二分查找
```java
public class OptimizedWeightedRandom {
private double[] prefixSum;
public OptimizedWeightedRandom(List<Double> weights) {
prefixSum = new double[weights.size()];
double sum = 0;
for (int i = 0; i < weights.size(); i++) {
sum += weights.get(i);
prefixSum[i] = sum;
}
}
public int nextIndex() {
double random = Math.random() * prefixSum[prefixSum.length-1];
int index = Arrays.binarySearch(prefixSum, random);
return Math.abs(index + 1);
}
}
```
时间复杂度:$O(\log n)$,适合大规模数据[^1]
#### 四、TreeMap实现方案
```java
public class TreeMapWeightedRandom {
private TreeMap<Double, Integer> map = new TreeMap<>();
private double totalWeight;
public void addItem(int index, double weight) {
totalWeight += weight;
map.put(totalWeight, index);
}
public int nextIndex() {
double random = Math.random() * totalWeight;
return map.ceilingEntry(random).getValue();
}
}
```
利用红黑树特性实现$O(\log n)$查询效率[^3]
#### 五、应用场景
1. 游戏抽奖系统(不同概率的奖品掉落)
2. 负载均衡(带权重的服务节点选择)
3. 推荐系统(内容按权重曝光)
#### 六、性能对比
| 方法 | 初始化耗时 | 查询耗时 | 适用场景 |
|-----------------|------------|----------|-----------------|
| 线性扫描 | $O(1)$ | $O(n)$ | 小数据集 |
| 前缀和+二分查找 | $O(n)$ | $O(\log n)$ | 静态权重 |
| TreeMap | $O(n\log n)$ | $O(\log n)$ | 动态更新权重 |
#### 七、注意事项
1. 权重值必须为非负数
2. 总权重不能为0
3. 浮点数精度问题(建议使用BigDecimal处理货币等场景)
package com.xymzsfxy.backend.service; import com.xymzsfxy.backend.entity.History; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; import java.util.List; import java.util.Random; @Component public class MyJobService { @Autowired private HistoryService historyService; @Scheduled(cron = "*/5 * * * * *") public void updatePriceHistory() { System.out.println("开始更新啦"); List<History> priceHistories = historyService.findAll(); Random random = new Random(); priceHistories.forEach(history -> { BigDecimal newPrice = new BigDecimal(random.nextDouble() * 1000); history.setPrice(newPrice); history.setLogisticsCost(new BigDecimal(random.nextDouble() * 100)); history.setTotalCost(history.getPrice().add(history.getLogisticsCost())); history.setRating(new BigDecimal(random.nextDouble() * 5).setScale(1, RoundingMode.HALF_UP)); history.setCrawlTime(LocalDateTime.now()); // 比较新价格与最高价和最低价 BigDecimal currentMaxPrice = history.getMaxPrice(); BigDecimal currentMinPrice = history.getMinPrice(); if (currentMaxPrice == null || newPrice.compareTo(currentMaxPrice) > 0) { history.setMaxPrice(newPrice); System.out.println(newPrice); } if (currentMinPrice == null || newPrice.compareTo(currentMinPrice) < 0) { history.setMinPrice(newPrice); System.out.println(newPrice); } String[] sources = {"天猫", "淘宝"}; int index = random.nextInt(sources.length); history.setSource(sources[index]); }); historyService.batchUpdate(priceHistories); // 批量更新 } }数据只保留两位小数,修改之后解读一下代码,currentMinPrice == null || newPrice.compareTo(currentMinPrice) < 0 目前currentMinPrice 和最大价格都初始化为0不是null给出修改后的完整代码
<think>好的,我需要帮助用户解决他们提供的Java代码中的数据精度问题,并修改相关的逻辑。首先,用户提到数据只保留两位小数,所以我要检查代码中所有BigDecimal的创建和设置部分,确保它们都使用了正确的舍入模式,并且保留两位小数。
首先看用户提供的代码,在updatePriceHistory方法中,newPrice是用random.nextDouble()生成一个随机数,然后乘以1000,创建为BigDecimal。这里的问题在于,new BigDecimal(random.nextDouble() * 1000)会得到一个未经四舍五入的小数,可能有很多位小数。用户要求保留两位小数,所以这里应该使用setScale(2, RoundingMode.HALF_UP)来限制小数位数。
同样,LogisticsCost和Rating的设置也存在同样的问题。例如,物流成本是random.nextDouble() * 100,创建BigDecimal后也需要保留两位小数。评分部分已经使用了setScale(1, RoundingMode.HALF_UP),但用户需求是保留两位小数,可能需要调整,不过根据原代码,评分可能应该是保留一位小数,这部分需要确认。但用户问题中明确数据保留两位,所以可能需要调整评分的小数位数,但原代码可能设计如此,需要进一步明确。不过用户当前的问题是关于价格等数据保留两位,所以重点在price、logisticsCost、totalCost这些字段。
接下来是currentMinPrice和currentMaxPrice的初始化问题。用户指出目前这两个字段初始化为0而不是null,这可能导致在比较时出现问题。例如,如果初始值是0,当新价格是正数时,最小值可能永远无法更新,因为0比任何正数都小。所以需要修改初始值为null,或者在第一次比较时处理。
在原来的代码中,判断条件是if (currentMaxPrice == null || newPrice.compareTo(currentMaxPrice) > 0),但用户说现在currentMinPrice和maxPrice都初始化为0而不是null。因此,当第一次比较时,currentMaxPrice是0,如果新价格是负数(但根据生成逻辑,random.nextDouble()生成的是0到1之间的数,乘以1000后是0到1000,所以newPrice应该是非负的,所以最大值初始化为0的话,第一次生成的newPrice如果是正数就会比0大,从而更新最大值。而最小值初始化为0的话,如果newPrice是正数,那么0更小,所以最小值不会被更新,除非newPrice比0小,但这里生成的价格都是非负的,所以最小值会保持0,这可能不符合预期,因为实际价格可能都是正数,导致最小值一直为0,无法正确反映实际的最小值。
因此,用户的代码需要将maxPrice和minPrice的初始值设为null,或者在第一次更新时设置为第一个newPrice的值。或者,在数据库中,这些字段的初始值可能为null,当从数据库查询出来时,如果字段为null,则进行相应的处理。
现在用户希望修改代码,使得currentMinPrice和maxPrice初始化为0,但这样会导致上述问题。所以正确的做法应该是将初始值设为null,或者在第一次比较时正确处理。或者,在实体类中,maxPrice和minPrice的初始值可能应该为null,而不是0。
根据用户的需求,他们可能已经将数据库中这些字段的默认值设置为0,或者在实体类中初始化为了0。现在需要修改代码,以正确处理初始值为0的情况,或者将初始值改为null。
假设用户的实体类中,maxPrice和minPrice的初始值为0,那么当第一次比较时,如果新价格是500,那么currentMaxPrice是0,newPrice.compareTo(currentMaxPrice) >0 为true,所以会更新maxPrice为500。对于minPrice,同样,currentMinPrice是0,如果newPrice是500,那么newPrice.compareTo(currentMinPrice) <0 为false,所以不会更新,minPrice保持0。但实际中,所有生成的价格都是0到1000之间的数,所以最小值会被初始化为0,而实际生成的价格都是大于等于0的,所以最小值可能始终是0,这可能不符合用户的预期,因为实际的最小值应该是所有生成的价格中的最小者,而不是0。
因此,正确的做法应该是在第一次比较时,如果currentMaxPrice是0,而newPrice是正数,那么应该将maxPrice更新为newPrice。同样,对于minPrice,如果currentMinPrice是0,而newPrice是正数,那么只有当newPrice小于0时才更新,但这不可能,因为newPrice是0到1000之间的数。因此,minPrice会一直保持0,这显然不对。
这说明原来的代码逻辑有问题,当初始值设置为0时,无法正确跟踪实际的最小值,因为生成的价格都是非负的,而0是最小可能的值。因此,正确的做法应该是将maxPrice和minPrice的初始值设为null,或者在第一次更新时设置为第一个newPrice的值。
因此,用户希望修改代码,使得即使currentMaxPrice和currentMinPrice初始化为0(而不是null),代码仍然能正确更新最大值和最小值。或者,用户可能需要将初始值改为null,并在代码中处理null的情况。
根据用户的问题描述,他们指出目前currentMinPrice和maxPrice都初始化为0而不是null,所以需要修改代码中的条件判断,使其在初始值为0的情况下仍能正确更新。
在原来的代码中,判断条件是:
if (currentMaxPrice == null || newPrice.compareTo(currentMaxPrice) > 0)
但此时currentMaxPrice是0,所以currentMaxPrice == null是false,因此会执行newPrice.compareTo(currentMaxPrice) >0。如果newPrice大于0,就会更新maxPrice为newPrice,这是正确的。
对于minPrice,判断条件是:
if (currentMinPrice == null || newPrice.compareTo(currentMinPrice) < 0)
同样,currentMinPrice是0,所以会检查newPrice是否小于0。但由于newPrice是0到1000之间的数,所以newPrice.compareTo(0) >=0,因此minPrice不会被更新,保持0。这会导致minPrice始终为0,而实际生成的价格中最小值可能大于0,因此这个逻辑是错误的。
因此,用户的问题在于,当初始值设置为0时,minPrice无法正确更新,因为所有生成的价格都是非负的,而初始的0会被认为是更小的值。
解决这个问题的方法是将maxPrice和minPrice的初始值设为null,或者在第一次比较时,如果初始值是0,则将其更新为第一个newPrice的值。或者,在生成价格时,避免生成0,但这样可能不符合业务需求。
因此,正确的修改应该是将maxPrice和minPrice的初始值设为null,或者在代码中处理初始值为0的情况。例如,在判断条件中,如果currentMaxPrice是0,并且是第一次更新,则将其替换为newPrice。
不过,根据用户提供的代码,当从数据库查询出History对象时,可能这些对象的maxPrice和minPrice已经被初始化为0,而不是null。因此,用户需要修改代码,使得在第一次更新时,即使maxPrice和minPrice是0,也会被正确更新为实际的newPrice,如果newPrice更大或更小的话。
但问题在于,如果初始maxPrice是0,而生成的newPrice是500,那么500比0大,所以maxPrice会被更新为500,这是正确的。但对于minPrice,如果newPrice是500,它比0大,所以minPrice不会被更新,保持0,但实际所有生成的价格都大于等于0,所以最小值应该是所有生成的价格中的最小值,而不是0。例如,如果所有生成的价格都在100到1000之间,那么minPrice应该被更新为100,而不是保持0。
所以,问题出在minPrice的初始值为0,导致无法正确跟踪实际的最小值,因为实际的价格都比0大或等于0。因此,正确的做法应该是将minPrice的初始值设为null,或者在第一次更新时,将minPrice设置为第一个newPrice的值,然后后续比较时更新。
因此,用户需要修改代码中的判断条件,以处理初始值为0的情况。例如,可以检查currentMinPrice是否为0,如果是,则将其视为null的情况,进行更新。
或者,用户可能应该在数据库中将这些字段的初始值设为null,而不是0。这样,在第一次比较时,currentMinPrice是null,所以会将newPrice设置为minPrice。
但根据用户的问题描述,他们希望修改代码,使得即使currentMinPrice和maxPrice初始化为0(不是null),代码仍能正确更新最大值和最小值。因此,我们需要调整判断条件。
例如,在判断minPrice时,如果currentMinPrice是0,并且newPrice大于0,那么是否需要将currentMinPrice更新为newPrice?因为此时0比所有生成的newPrice都小,所以minPrice应该保持0,但实际中这可能不是用户想要的,因为用户可能希望minPrice是实际生成的价格中的最小值,而不是包括初始的0。
这说明原来的业务逻辑可能存在问题,如果maxPrice和minPrice的初始值是0,而实际生成的价格都是正数,那么maxPrice会被正确更新,但minPrice将保持0,这可能不符合实际需求,因为实际的最小值应该是生成的价格中的最小值,而不是0。
因此,正确的做法应该是将maxPrice和minPrice的初始值设为null,或者在第一次更新时忽略初始的0值。
因此,在代码中,可能需要调整判断条件,例如:
对于maxPrice:
if (currentMaxPrice == null || currentMaxPrice.equals(BigDecimal.ZERO) || newPrice.compareTo(currentMaxPrice) > 0)
同样,对于minPrice:
if (currentMinPrice == null || currentMinPrice.equals(BigDecimal.ZERO) || newPrice.compareTo(currentMinPrice) < 0)
这样,如果currentMaxPrice是0,则视为需要更新,将maxPrice设为newPrice。或者,这可能不符合用户的实际需求,需要进一步确认。
但根据用户的问题,他们可能希望将maxPrice和minPrice初始化为0,并且在后续更新中正确跟踪最大值和最小值,其中初始的0可能不是实际数据的一部分。例如,可能希望maxPrice和minPrice只反映实际生成的价格中的最大值和最小值,而不包括初始的0。因此,在第一次更新时,应该将maxPrice和minPrice设置为第一个生成的newPrice,然后在后续比较中更新。
因此,修改判断条件为:
对于maxPrice:
if (currentMaxPrice == null || currentMaxPrice.compareTo(BigDecimal.ZERO) == 0 || newPrice.compareTo(currentMaxPrice) > 0)
对于minPrice:
if (currentMinPrice == null || currentMinPrice.compareTo(BigDecimal.ZERO) == 0 || newPrice.compareTo(currentMinPrice) < 0)
这样,如果currentMaxPrice是0,则将其视为需要更新,从而将maxPrice设为newPrice。然后,在后续的更新中,再比较newPrice和当前的maxPrice。
但这样处理的话,在第一次更新时,如果currentMaxPrice是0,而newPrice是500,那么会将maxPrice设为500,而之后的比较将正确进行。对于minPrice,如果currentMinPrice是0,而newPrice是500,那么由于500 > 0,条件不满足,所以minPrice保持0。但实际中,所有生成的价格都>=0,所以minPrice可能始终是0,除非有newPrice等于0,但random.nextDouble()生成的是0到1之间的数,所以newPrice可能是0(当random.nextDouble()为0时,0*1000=0)。此时,如果newPrice是0,那么currentMinPrice是0,条件判断会更新吗?
例如,如果currentMinPrice是0,而newPrice是0,那么newPrice.compareTo(currentMinPrice) <0 是false,所以不会更新。因此,minPrice保持0,这是正确的。
但如果newPrice是100,currentMinPrice是0,条件判断会跳过,minPrice保持0,而实际的最小值是100,但初始的0可能不是实际数据的一部分,导致minPrice不正确。
这说明,如果maxPrice和minPrice的初始值是0,而实际数据中没有0,那么minPrice将被错误地保持为0,而不是实际数据中的最小值。
因此,正确的解决方案应该是在第一次更新时,将maxPrice和minPrice设置为第一个生成的newPrice,而不是初始的0。因此,在数据库中,maxPrice和minPrice应该初始化为null,而不是0。或者在代码中,当从数据库获取History对象时,如果maxPrice或minPrice为0,则视为null,进行更新。
因此,用户可能需要修改他们的实体类,将maxPrice和minPrice的初始值设为null,或者在查询时处理这些字段的初始值。
但根据用户的问题,他们可能无法修改数据库中的初始值,因此需要在代码中处理这种情况。
因此,修改代码中的判断条件,将currentMaxPrice或currentMinPrice为0的情况视为null的情况处理。
例如,将条件改为:
if (currentMaxPrice == null || currentMaxPrice.equals(BigDecimal.ZERO) || newPrice.compareTo(currentMaxPrice) > 0)
同样对于minPrice:
if (currentMinPrice == null || currentMinPrice.equals(BigDecimal.ZERO) || newPrice.compareTo(currentMinPrice) < 0)
这样,当currentMaxPrice是0时,会将其视为需要更新,从而将maxPrice设置为newPrice。这样,在第一次循环时,maxPrice和minPrice都会被更新为第一个newPrice的值,然后在后续的循环中,继续比较和更新。
例如,假设第一个newPrice是500,那么maxPrice会被设置为500,而minPrice会被设置为500(因为currentMinPrice是0,条件成立,所以更新为500)。然后,后续的newPrice如果比500小,minPrice会继续更新;如果比500大,maxPrice会更新。
这样,初始的0被替换为第一个newPrice的值,从而正确跟踪最大值和最小值。
因此,用户需要修改判断条件,加入对currentMaxPrice和currentMinPrice是否为0的判断。
此外,用户还要求将所有数据保留两位小数,因此需要确保所有BigDecimal的创建都使用setScale(2, RoundingMode.HALF_UP)。
现在,根据以上分析,修改代码的步骤如下:
1. 在生成newPrice、logisticsCost时,使用setScale(2, RoundingMode.HALF_UP)保留两位小数。
2. 修改maxPrice和minPrice的判断条件,将currentMaxPrice和currentMinPrice为0的情况视为需要更新。
修改后的代码:
在生成newPrice时:
BigDecimal newPrice = new BigDecimal(random.nextDouble() * 1000).setScale(2, RoundingMode.HALF_UP);
同样,logisticsCost:
history.setLogisticsCost(new BigDecimal(random.nextDouble() * 100).setScale(2, RoundingMode.HALF_UP));
评分部分可能应该保留一位小数,如原代码中的setScale(1),但用户要求数据保留两位小数,可能需要调整,但原问题没有提到评分,所以可能保持原样。
然后,在比较maxPrice和minPrice时,修改条件:
if (currentMaxPrice == null || currentMaxPrice.compareTo(BigDecimal.ZERO) == 0 || newPrice.compareTo(currentMaxPrice) > 0)
和
if (currentMinPrice == null || currentMinPrice.compareTo(BigDecimal.ZERO) == 0 || newPrice.compareTo(currentMinPrice) < 0)
这样,当currentMaxPrice是0时,条件成立,会更新为newPrice。
这样修改后,maxPrice和minPrice将正确反映实际生成的价格中的最大值和最小值,而不是初始的0。
现在,整合所有修改,给出完整的代码:
修改后的完整代码:
package com.xymzsfxy.backend.service;
import com.xymzsfxy.backend.entity.History;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Random;
@Component
public class MyJobService {
@Autowired
private HistoryService historyService;
@Scheduled(cron = "*/5 * * * * *")
public void updatePriceHistory() {
System.out.println("开始更新啦");
List<History> priceHistories = historyService.findAll();
Random random = new Random();
priceHistories.forEach(history -> {
// 生成随机价格并保留两位小数
BigDecimal newPrice = new BigDecimal(random.nextDouble() * 1000).setScale(2, RoundingMode.HALF_UP);
history.setPrice(newPrice);
// 生成物流成本并保留两位小数
BigDecimal logisticsCost = new BigDecimal(random.nextDouble() * 100).setScale(2, RoundingMode.HALF_UP);
history.setLogisticsCost(logisticsCost);
// 计算总成本(价格+物流成本)
history.setTotalCost(newPrice.add(logisticsCost));
// 生成评分(1位小数)
history.setRating(new BigDecimal(random.nextDouble() * 5).setScale(1, RoundingMode.HALF_UP));
history.setCrawlTime(LocalDateTime.now());
// 获取当前最大/最小价格
BigDecimal currentMaxPrice = history.getMaxPrice();
BigDecimal currentMinPrice = history.getMinPrice();
// 更新最大值逻辑:如果当前最大值是null/0,或新价格更高
if (currentMaxPrice == null
|| currentMaxPrice.compareTo(BigDecimal.ZERO) == 0
|| newPrice.compareTo(currentMaxPrice) > 0) {
history.setMaxPrice(newPrice);
}
// 更新最小值逻辑:如果当前最小值是null/0,或新价格更低
if (currentMinPrice == null
|| currentMinPrice.compareTo(BigDecimal.ZERO) == 0
|| newPrice.compareTo(currentMinPrice) < 0) {
history.setMinPrice(newPrice);
}
// 随机设置数据来源
String[] sources = {"天猫", "淘宝"};
history.setSource(sources[random.nextInt(sources.length)]);
});
// 批量更新数据库
historyService.batchUpdate(priceHistories);
}
}
然后,解读代码修改点:
1. 小数位数控制:
- 价格(newPrice)和物流成本(logisticsCost)在创建时都使用setScale(2, RoundingMode.HALF_UP)保留两位小数。
2. 极值更新逻辑优化:
- 在比较最大值时,增加了对初始值0的判断:currentMaxPrice.compareTo(BigDecimal.ZERO) == 0
- 当最大/最小值为0时(初始状态),直接接受第一个有效价格作为极值
- 后续比较将基于实际生成的价格进行动态更新
需要注意的潜在问题:
- 评分部分仍保留1位小数(与用户需求中的两位小数不符?需要确认用户是否要求评分也保留两位。但原问题可能仅针对价格相关字段,所以可能不需要修改评分部分)
最后,确保所有BigDecimal的创建都正确设置了小数位数,并且在比较时正确处理了初始值为0的情况。</think>以下是对修改后代码的详细解读:
### 修改点说明
#### 1. 数据精度控制
```java
// 价格生成(0~1000范围)
BigDecimal newPrice = new BigDecimal(random.nextDouble() * 1000)
.setScale(2, RoundingMode.HALF_UP); // 新增四舍五入
// 物流成本生成(0~100范围)
BigDecimal logisticsCost = new BigDecimal(random.nextDouble() * 100)
.setScale(2, RoundingMode.HALF_UP); // 新增四舍五入
```
- **作用**:通过`setScale(2, RoundingMode.HALF_UP)`确保所有数值保留两位小数
- **示例**:
- 原始随机数:`123.456789` → 处理后:`123.46`
- 原始随机数:`78.901234` → 处理后:`78.90`
#### 2. 极值初始化逻辑优化
```java
// 修改后的极值判断条件
if (currentMaxPrice == null
|| currentMaxPrice.compareTo(BigDecimal.ZERO) == 0
|| newPrice.compareTo(currentMaxPrice) > 0) {
// 更新最大值逻辑
}
if (currentMinPrice == null
|| currentMinPrice.compareTo(BigDecimal.ZERO) == 0
|| newPrice.compareTo(currentMinPrice) < 0) {
// 更新最小值逻辑
}
```
- **核心改进**:
1. 增加对`BigDecimal.ZERO`的显式判断
2. 解决初始值为0时无法正确跟踪实际极值的问题
- **效果**:
- 首次运行时,0值会被实际价格覆盖
- 后续运行时会正确进行极值比较
### 完整代码结构
```java
package com.xymzsfxy.backend.service;
import com.xymzsfxy.backend.entity.History;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Random;
@Component
public class MyJobService {
@Autowired
private HistoryService historyService;
@Scheduled(cron = "*/5 * * * * *")
public void updatePriceHistory() {
System.out.println("开始更新啦");
List<History> priceHistories = historyService.findAll();
Random random = new Random();
priceHistories.forEach(history -> {
// 生成带两位小数的价格
BigDecimal newPrice = new BigDecimal(random.nextDouble() * 1000)
.setScale(2, RoundingMode.HALF_UP);
history.setPrice(newPrice);
// 生成带两位小数的物流成本
BigDecimal logisticsCost = new BigDecimal(random.nextDouble() * 100)
.setScale(2, RoundingMode.HALF_UP);
history.setLogisticsCost(logisticsCost);
// 计算总成本
history.setTotalCost(newPrice.add(logisticsCost));
// 生成评分(保持原1位小数设计)
history.setRating(new BigDecimal(random.nextDouble() * 5)
.setScale(1, RoundingMode.HALF_UP));
history.setCrawlTime(LocalDateTime.now());
// 极值比较逻辑
BigDecimal currentMaxPrice = history.getMaxPrice();
if (currentMaxPrice == null
|| currentMaxPrice.compareTo(BigDecimal.ZERO) == 0
|| newPrice.compareTo(currentMaxPrice) > 0) {
history.setMaxPrice(newPrice);
}
BigDecimal currentMinPrice = history.getMinPrice();
if (currentMinPrice == null
|| currentMinPrice.compareTo(BigDecimal.ZERO) == 0
|| newPrice.compareTo(currentMinPrice) < 0) {
history.setMinPrice(newPrice);
}
// 随机数据源设置
String[] sources = {"天猫", "淘宝"};
history.setSource(sources[random.nextInt(sources.length)]);
});
historyService.batchUpdate(priceHistories);
}
}
```
### 关键流程说明
1. **定时任务**:每5秒执行一次(通过`@Scheduled(cron = "*/5 * * * * *")`配置)
2. **数据生成**:
- 价格范围:$0.00 \leq price < 1000.00$
- 物流成本范围:$0.00 \leq logistics < 100.00$
- 总成本计算:$total = price + logistics$(自动保持两位小数精度)
3. **极值追踪**:
- 首次运行时自动用有效价格替换初始0值
- 后续通过比较运算动态更新极值
4. **数据持久化**:通过`historyService.batchUpdate()`实现批量更新
### 数学验证示例
假设某商品历史记录的初始极值为0:
- 第一次生成价格:$852.37$
- 最大值更新:$0 \rightarrow 852.37$
- 最小值更新:$0 \rightarrow 852.37$(因为初始0被识别为无效状态)
- 第二次生成价格:$639.15$
- 最大值保持:$852.37$
- 最小值更新:$852.37 \rightarrow 639.15$
- 第三次生成价格:$921.44$
- 最大值更新:$852.37 \rightarrow 921.44$
- 最小值保持:$639.15$
该逻辑确保极值始终反映实际生成数据的波动范围。
阅读全文
相关推荐
















